Identify users using their remote ID (#1732)

This commit is contained in:
qwerty287 2023-05-11 05:19:35 +02:00 committed by GitHub
parent 02cfbc8cbf
commit 6d2240b2e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 101 additions and 47 deletions

View file

@ -65,7 +65,7 @@ func HandleAuth(c *gin.Context) {
config := ToConfig(c)
// get the user from the database
u, err := _store.GetUserLogin(tmpuser.Login)
u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login)
if err != nil {
if !errors.Is(err, types.RecordNotExist) {
_ = c.AbortWithError(http.StatusInternalServerError, err)
@ -92,11 +92,12 @@ func HandleAuth(c *gin.Context) {
// create the user account
u = &model.User{
Login: tmpuser.Login,
Token: tmpuser.Token,
Secret: tmpuser.Secret,
Email: tmpuser.Email,
Avatar: tmpuser.Avatar,
Login: tmpuser.Login,
ForgeRemoteID: tmpuser.ForgeRemoteID,
Token: tmpuser.Token,
Secret: tmpuser.Secret,
Email: tmpuser.Email,
Avatar: tmpuser.Avatar,
Hash: base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32),
),
@ -115,6 +116,8 @@ func HandleAuth(c *gin.Context) {
u.Secret = tmpuser.Secret
u.Email = tmpuser.Email
u.Avatar = tmpuser.Avatar
u.ForgeRemoteID = tmpuser.ForgeRemoteID
u.Login = tmpuser.Login
u.Admin = u.Admin || config.IsAdmin(tmpuser)
// if self-registration is enabled for whitelisted organizations we need to

View file

@ -118,11 +118,12 @@ func cloneLink(repo *internal.Repo) string {
// structure to the Woodpecker User structure.
func convertUser(from *internal.Account, token *oauth2.Token) *model.User {
return &model.User{
Login: from.Login,
Token: token.AccessToken,
Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(),
Avatar: from.Links.Avatar.Href,
Login: from.Login,
Token: token.AccessToken,
Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(),
Avatar: from.Links.Avatar.Href,
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)),
}
}

View file

@ -21,6 +21,7 @@ import (
)
type Account struct {
ID int64 `json:"id"`
Login string `json:"username"`
Name string `json:"display_name"`
Type string `json:"type"`

View file

@ -121,10 +121,11 @@ func convertPushHook(hook *internal.PostHook, baseURL string) *model.Pipeline {
// structure to the Woodpecker User structure.
func convertUser(from *internal.User, token *oauth.AccessToken) *model.User {
return &model.User{
Login: from.Slug,
Token: token.Token,
Email: from.EmailAddress,
Avatar: avatarLink(from.EmailAddress),
Login: from.Slug,
Token: token.Token,
Email: from.EmailAddress,
Avatar: avatarLink(from.EmailAddress),
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)),
}
}

View file

@ -70,29 +70,27 @@ func NewClientWithToken(ctx context.Context, url string, consumer *oauth.Consume
}
func (c *Client) FindCurrentUser() (*User, error) {
CurrentUserIDResponse, err := c.doGet(fmt.Sprintf(currentUserID, c.base))
if CurrentUserIDResponse != nil {
defer CurrentUserIDResponse.Body.Close()
}
currentUserIDResponse, err := c.doGet(fmt.Sprintf(currentUserID, c.base))
if err != nil {
return nil, err
}
defer currentUserIDResponse.Body.Close()
bits, err := io.ReadAll(CurrentUserIDResponse.Body)
bits, err := io.ReadAll(currentUserIDResponse.Body)
if err != nil {
return nil, err
}
login := string(bits)
CurrentUserResponse, err := c.doGet(fmt.Sprintf(pathUser, c.base, login))
if CurrentUserResponse != nil {
defer CurrentUserResponse.Body.Close()
currentUserResponse, err := c.doGet(fmt.Sprintf(pathUser, c.base, login))
if currentUserResponse != nil {
defer currentUserResponse.Body.Close()
}
if err != nil {
return nil, err
}
contents, err := io.ReadAll(CurrentUserResponse.Body)
contents, err := io.ReadAll(currentUserResponse.Body)
if err != nil {
return nil, err
}

View file

@ -143,12 +143,13 @@ func (c *Gitea) Login(ctx context.Context, w http.ResponseWriter, req *http.Requ
}
return &model.User{
Token: token.AccessToken,
Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(),
Login: account.UserName,
Email: account.Email,
Avatar: expandAvatar(c.URL, account.AvatarURL),
Token: token.AccessToken,
Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(),
Login: account.UserName,
Email: account.Email,
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(account.ID)),
Avatar: expandAvatar(c.URL, account.AvatarURL),
}, nil
}

View file

@ -129,10 +129,11 @@ func (c *client) Login(ctx context.Context, res http.ResponseWriter, req *http.R
}
return &model.User{
Login: *user.Login,
Email: *email.Email,
Token: token.AccessToken,
Avatar: *user.AvatarURL,
Login: user.GetLogin(),
Email: email.GetEmail(),
Token: token.AccessToken,
Avatar: user.GetAvatarURL(),
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(user.GetID())),
}, nil
}

View file

@ -133,11 +133,12 @@ func (g *GitLab) Login(ctx context.Context, res http.ResponseWriter, req *http.R
}
user := &model.User{
Login: login.Username,
Email: login.Email,
Avatar: login.AvatarURL,
Token: token.AccessToken,
Secret: token.RefreshToken,
Login: login.Username,
Email: login.Email,
Avatar: login.AvatarURL,
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(login.ID)),
Token: token.AccessToken,
Secret: token.RefreshToken,
}
if !strings.HasPrefix(user.Avatar, "http") {
user.Avatar = g.URL + "/" + login.AvatarURL

View file

@ -18,6 +18,7 @@ package gogs
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"net/url"
@ -118,10 +119,11 @@ func (c *client) Login(_ context.Context, res http.ResponseWriter, req *http.Req
}
return &model.User{
Token: accessToken,
Login: account.UserName,
Email: account.Email,
Avatar: expandAvatar(c.URL, account.AvatarUrl),
Token: accessToken,
Login: account.UserName,
Email: account.Email,
Avatar: expandAvatar(c.URL, account.AvatarUrl),
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(account.ID)),
}, nil
}

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.23.1. DO NOT EDIT.
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks

View file

@ -34,6 +34,8 @@ type User struct {
// required: true
ID int64 `json:"id" xorm:"pk autoincr 'user_id'"`
ForgeRemoteID ForgeRemoteID `json:"-" xorm:"forge_remote_id"`
// Login is the username for this user.
//
// required: true

View file

@ -16,6 +16,7 @@ package datastore
import (
"github.com/woodpecker-ci/woodpecker/server/model"
"xorm.io/xorm"
)
func (s storage) GetUser(id int64) (*model.User, error) {
@ -23,9 +24,23 @@ func (s storage) GetUser(id int64) (*model.User, error) {
return user, wrapGet(s.engine.ID(id).Get(user))
}
func (s storage) GetUserLogin(login string) (*model.User, error) {
func (s storage) GetUserRemoteID(remoteID model.ForgeRemoteID, login string) (*model.User, error) {
sess := s.engine.NewSession()
user := new(model.User)
return user, wrapGet(s.engine.Where("user_login=?", login).Get(user))
err := wrapGet(sess.Where("forge_remote_id = ?", remoteID).Get(user))
if err != nil {
user, err = s.getUserLogin(sess, login)
}
return user, err
}
func (s storage) GetUserLogin(login string) (*model.User, error) {
return s.getUserLogin(s.engine.NewSession(), login)
}
func (s storage) getUserLogin(sess *xorm.Session, login string) (*model.User, error) {
user := new(model.User)
return user, wrapGet(sess.Where("user_login=?", login).Get(user))
}
func (s storage) GetUserList(p *model.ListOptions) ([]*model.User, error) {

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.23.1. DO NOT EDIT.
// Code generated by mockery v2.26.1. DO NOT EDIT.
package mocks
@ -1079,6 +1079,32 @@ func (_m *Store) GetUserLogin(_a0 string) (*model.User, error) {
return r0, r1
}
// GetUserRemoteID provides a mock function with given fields: _a0, _a1
func (_m *Store) GetUserRemoteID(_a0 model.ForgeRemoteID, _a1 string) (*model.User, error) {
ret := _m.Called(_a0, _a1)
var r0 *model.User
var r1 error
if rf, ok := ret.Get(0).(func(model.ForgeRemoteID, string) (*model.User, error)); ok {
return rf(_a0, _a1)
}
if rf, ok := ret.Get(0).(func(model.ForgeRemoteID, string) *model.User); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.User)
}
}
if rf, ok := ret.Get(1).(func(model.ForgeRemoteID, string) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GlobalSecretFind provides a mock function with given fields: _a0
func (_m *Store) GlobalSecretFind(_a0 string) (*model.Secret, error) {
ret := _m.Called(_a0)

View file

@ -29,6 +29,8 @@ type Store interface {
// Users
// GetUser gets a user by unique ID.
GetUser(int64) (*model.User, error)
// GetUserRemoteID gets a user by remote ID with fallback to login name.
GetUserRemoteID(model.ForgeRemoteID, string) (*model.User, error)
// GetUserLogin gets a user by unique Login name.
GetUserLogin(string) (*model.User, error)
// GetUserList gets a list of all users in the system.