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) config := ToConfig(c)
// get the user from the database // get the user from the database
u, err := _store.GetUserLogin(tmpuser.Login) u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login)
if err != nil { if err != nil {
if !errors.Is(err, types.RecordNotExist) { if !errors.Is(err, types.RecordNotExist) {
_ = c.AbortWithError(http.StatusInternalServerError, err) _ = c.AbortWithError(http.StatusInternalServerError, err)
@ -92,11 +92,12 @@ func HandleAuth(c *gin.Context) {
// create the user account // create the user account
u = &model.User{ u = &model.User{
Login: tmpuser.Login, Login: tmpuser.Login,
Token: tmpuser.Token, ForgeRemoteID: tmpuser.ForgeRemoteID,
Secret: tmpuser.Secret, Token: tmpuser.Token,
Email: tmpuser.Email, Secret: tmpuser.Secret,
Avatar: tmpuser.Avatar, Email: tmpuser.Email,
Avatar: tmpuser.Avatar,
Hash: base32.StdEncoding.EncodeToString( Hash: base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32),
), ),
@ -115,6 +116,8 @@ func HandleAuth(c *gin.Context) {
u.Secret = tmpuser.Secret u.Secret = tmpuser.Secret
u.Email = tmpuser.Email u.Email = tmpuser.Email
u.Avatar = tmpuser.Avatar u.Avatar = tmpuser.Avatar
u.ForgeRemoteID = tmpuser.ForgeRemoteID
u.Login = tmpuser.Login
u.Admin = u.Admin || config.IsAdmin(tmpuser) u.Admin = u.Admin || config.IsAdmin(tmpuser)
// if self-registration is enabled for whitelisted organizations we need to // 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. // structure to the Woodpecker User structure.
func convertUser(from *internal.Account, token *oauth2.Token) *model.User { func convertUser(from *internal.Account, token *oauth2.Token) *model.User {
return &model.User{ return &model.User{
Login: from.Login, Login: from.Login,
Token: token.AccessToken, Token: token.AccessToken,
Secret: token.RefreshToken, Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(), Expiry: token.Expiry.UTC().Unix(),
Avatar: from.Links.Avatar.Href, Avatar: from.Links.Avatar.Href,
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)),
} }
} }

View file

@ -21,6 +21,7 @@ import (
) )
type Account struct { type Account struct {
ID int64 `json:"id"`
Login string `json:"username"` Login string `json:"username"`
Name string `json:"display_name"` Name string `json:"display_name"`
Type string `json:"type"` 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. // structure to the Woodpecker User structure.
func convertUser(from *internal.User, token *oauth.AccessToken) *model.User { func convertUser(from *internal.User, token *oauth.AccessToken) *model.User {
return &model.User{ return &model.User{
Login: from.Slug, Login: from.Slug,
Token: token.Token, Token: token.Token,
Email: from.EmailAddress, Email: from.EmailAddress,
Avatar: avatarLink(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) { func (c *Client) FindCurrentUser() (*User, error) {
CurrentUserIDResponse, err := c.doGet(fmt.Sprintf(currentUserID, c.base)) currentUserIDResponse, err := c.doGet(fmt.Sprintf(currentUserID, c.base))
if CurrentUserIDResponse != nil {
defer CurrentUserIDResponse.Body.Close()
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer currentUserIDResponse.Body.Close()
bits, err := io.ReadAll(CurrentUserIDResponse.Body) bits, err := io.ReadAll(currentUserIDResponse.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
login := string(bits) login := string(bits)
CurrentUserResponse, err := c.doGet(fmt.Sprintf(pathUser, c.base, login)) currentUserResponse, err := c.doGet(fmt.Sprintf(pathUser, c.base, login))
if CurrentUserResponse != nil { if currentUserResponse != nil {
defer CurrentUserResponse.Body.Close() defer currentUserResponse.Body.Close()
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
contents, err := io.ReadAll(CurrentUserResponse.Body) contents, err := io.ReadAll(currentUserResponse.Body)
if err != nil { if err != nil {
return nil, err 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{ return &model.User{
Token: token.AccessToken, Token: token.AccessToken,
Secret: token.RefreshToken, Secret: token.RefreshToken,
Expiry: token.Expiry.UTC().Unix(), Expiry: token.Expiry.UTC().Unix(),
Login: account.UserName, Login: account.UserName,
Email: account.Email, Email: account.Email,
Avatar: expandAvatar(c.URL, account.AvatarURL), ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(account.ID)),
Avatar: expandAvatar(c.URL, account.AvatarURL),
}, nil }, nil
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -16,6 +16,7 @@ package datastore
import ( import (
"github.com/woodpecker-ci/woodpecker/server/model" "github.com/woodpecker-ci/woodpecker/server/model"
"xorm.io/xorm"
) )
func (s storage) GetUser(id int64) (*model.User, error) { 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)) 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) 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) { 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 package mocks
@ -1079,6 +1079,32 @@ func (_m *Store) GetUserLogin(_a0 string) (*model.User, error) {
return r0, r1 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 // GlobalSecretFind provides a mock function with given fields: _a0
func (_m *Store) GlobalSecretFind(_a0 string) (*model.Secret, error) { func (_m *Store) GlobalSecretFind(_a0 string) (*model.Secret, error) {
ret := _m.Called(_a0) ret := _m.Called(_a0)

View file

@ -29,6 +29,8 @@ type Store interface {
// Users // Users
// GetUser gets a user by unique ID. // GetUser gets a user by unique ID.
GetUser(int64) (*model.User, error) 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 gets a user by unique Login name.
GetUserLogin(string) (*model.User, error) GetUserLogin(string) (*model.User, error)
// GetUserList gets a list of all users in the system. // GetUserList gets a list of all users in the system.