You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	Support multiple users with same login name but different forges (#5612)
Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
This commit is contained in:
		| @@ -4476,6 +4476,19 @@ const docTemplate = `{ | ||||
|                         "name": "login", | ||||
|                         "in": "path", | ||||
|                         "required": true | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "description": "specify forge (else default will be used)", | ||||
|                         "name": "forge_id", | ||||
|                         "in": "query", | ||||
|                         "required": true | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "description": "specify user id at forge (else fallback to login)", | ||||
|                         "name": "forge_remote_id", | ||||
|                         "in": "query" | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
| @@ -4511,6 +4524,19 @@ const docTemplate = `{ | ||||
|                         "name": "login", | ||||
|                         "in": "path", | ||||
|                         "required": true | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "description": "specify forge (else default will be used)", | ||||
|                         "name": "forge_id", | ||||
|                         "in": "query", | ||||
|                         "required": true | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "description": "specify user id at forge (else fallback to login)", | ||||
|                         "name": "forge_remote_id", | ||||
|                         "in": "query" | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
| @@ -5552,6 +5578,9 @@ const docTemplate = `{ | ||||
|                 "forge_id": { | ||||
|                     "type": "integer" | ||||
|                 }, | ||||
|                 "forge_remote_id": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "id": { | ||||
|                     "description": "the id for this user.\n\nrequired: true", | ||||
|                     "type": "integer" | ||||
|   | ||||
| @@ -160,13 +160,29 @@ func HandleAuth(c *gin.Context) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	var user *model.User | ||||
|  | ||||
| 	// get the user from the database | ||||
| 	user, err := _store.GetUserRemoteID(userFromForge.ForgeRemoteID, userFromForge.Login) | ||||
| 	user, err = _store.GetUserByRemoteID(forgeID, userFromForge.ForgeRemoteID) | ||||
| 	if err != nil && !errors.Is(err, types.RecordNotExist) { | ||||
| 		log.Error().Err(err).Msgf("cannot get user %s", userFromForge.Login) | ||||
| 		c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error") | ||||
| 		return | ||||
| 	} | ||||
| 	// update user login (in case forge supports renaming) | ||||
| 	if user != nil { | ||||
| 		user.Login = userFromForge.Login | ||||
| 	} | ||||
|  | ||||
| 	// re-try with login name | ||||
| 	if user == nil || errors.Is(err, types.RecordNotExist) { | ||||
| 		user, err = _store.GetUserByLogin(forgeID, userFromForge.Login) | ||||
| 		if err != nil && !errors.Is(err, types.RecordNotExist) { | ||||
| 			log.Error().Err(err).Msgf("cannot get user %s", userFromForge.Login) | ||||
| 			c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/login?error=internal_error") | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if user == nil || errors.Is(err, types.RecordNotExist) { | ||||
| 		// if self-registration is disabled we should return a not authorized error | ||||
|   | ||||
| @@ -158,7 +158,8 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("GetUserByLogin", user.ForgeID, user.Login).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("CreateUser", mock.Anything).Return(nil) | ||||
| 		_store.On("OrgFindByName", user.Login, user.ForgeID).Return(nil, nil) | ||||
| 		_store.On("OrgCreate", mock.Anything).Return(nil) | ||||
| @@ -192,7 +193,7 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(user, nil) | ||||
| 		_store.On("OrgGet", org.ID).Return(org, nil) | ||||
| 		_store.On("UpdateUser", mock.Anything).Return(nil) | ||||
| 		_forge.On("Repos", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) | ||||
| @@ -224,7 +225,8 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("GetUserByLogin", user.ForgeID, user.Login).Return(nil, types.RecordNotExist) | ||||
|  | ||||
| 		api.HandleAuth(c) | ||||
|  | ||||
| @@ -285,7 +287,7 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(user, nil) | ||||
| 		_store.On("OrgFindByName", user.Login, user.ForgeID).Return(nil, types.RecordNotExist) | ||||
| 		_store.On("OrgCreate", mock.Anything).Return(nil) | ||||
| 		_store.On("UpdateUser", mock.Anything).Return(nil) | ||||
| @@ -319,7 +321,7 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(user, nil) | ||||
| 		_store.On("OrgFindByName", user.Login, user.ForgeID).Return(org, nil) | ||||
| 		_store.On("OrgUpdate", mock.Anything).Return(nil) | ||||
| 		_store.On("UpdateUser", mock.Anything).Return(nil) | ||||
| @@ -353,7 +355,7 @@ func TestHandleAuth(t *testing.T) { | ||||
|  | ||||
| 		_manager.On("ForgeByID", int64(1)).Return(_forge, nil) | ||||
| 		_forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) | ||||
| 		_store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) | ||||
| 		_store.On("GetUserByRemoteID", user.ForgeID, user.ForgeRemoteID).Return(user, nil) | ||||
| 		_store.On("OrgGet", user.OrgID).Return(org, nil) | ||||
| 		_store.On("OrgUpdate", mock.Anything).Return(nil) | ||||
| 		_store.On("UpdateUser", mock.Anything).Return(nil) | ||||
|   | ||||
| @@ -16,7 +16,10 @@ package api | ||||
|  | ||||
| import ( | ||||
| 	"encoding/base32" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	"github.com/google/tink/go/subtle/random" | ||||
| @@ -24,8 +27,11 @@ import ( | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/model" | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/store" | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/store/types" | ||||
| ) | ||||
|  | ||||
| const defaultForgeID = 1 | ||||
|  | ||||
| // GetUsers | ||||
| // | ||||
| //	@Summary		List users | ||||
| @@ -56,8 +62,23 @@ func GetUsers(c *gin.Context) { | ||||
| //	@Tags			Users | ||||
| //	@Param			Authorization	header	string	true	"Insert your personal access token"	default(Bearer <personal access token>) | ||||
| //	@Param			login			path	string	true	"the user's login name" | ||||
| //	@Param			forge_id		query	string	true	"specify forge (else default will be used)" | ||||
| //	@Param			forge_remote_id	query	string	false	"specify user id at forge (else fallback to login)" | ||||
| func GetUser(c *gin.Context) { | ||||
| 	user, err := store.FromContext(c).GetUserLogin(c.Param("login")) | ||||
| 	forgeID, err := strconv.ParseInt(c.DefaultQuery("forge_id", fmt.Sprint(defaultForgeID)), 10, 64) | ||||
| 	if err != nil { | ||||
| 		c.AbortWithStatus(http.StatusBadRequest) | ||||
| 		return | ||||
| 	} | ||||
| 	forgeRemoteID := model.ForgeRemoteID(c.Query("forge_remote_id")) | ||||
|  | ||||
| 	var user *model.User | ||||
|  | ||||
| 	if forgeRemoteID.IsValid() { | ||||
| 		user, err = store.FromContext(c).GetUserByRemoteID(forgeID, forgeRemoteID) | ||||
| 	} else { | ||||
| 		user, err = store.FromContext(c).GetUserByLogin(forgeID, c.Param("login")) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		handleDBError(c, err) | ||||
| 		return | ||||
| @@ -87,14 +108,26 @@ func PatchUser(c *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	user, err := _store.GetUserLogin(c.Param("login")) | ||||
| 	if err != nil { | ||||
| 	if in.ForgeID < defaultForgeID { | ||||
| 		in.ForgeID = defaultForgeID | ||||
| 	} | ||||
|  | ||||
| 	user, err := store.FromContext(c).GetUserByRemoteID(in.ForgeID, in.ForgeRemoteID) | ||||
| 	if err != nil && !errors.Is(err, types.RecordNotExist) { | ||||
| 		handleDBError(c, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// TODO: allow to change login (currently used as primary key) | ||||
| 	if user == nil { | ||||
| 		user, err = _store.GetUserByLogin(in.ForgeID, c.Param("login")) | ||||
| 		if err != nil { | ||||
| 			handleDBError(c, err) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// TODO: disallow to change login, email, avatar if the user is using oauth | ||||
| 	user.Login = in.Login | ||||
| 	user.Email = in.Email | ||||
| 	user.Avatar = in.Avatar | ||||
| 	user.Admin = in.Admin | ||||
| @@ -132,7 +165,7 @@ func PostUser(c *gin.Context) { | ||||
| 		Hash: base32.StdEncoding.EncodeToString( | ||||
| 			random.GetRandomBytes(32), | ||||
| 		), | ||||
| 		ForgeID:       1,                        // TODO: replace with forge id when multiple forges are supported | ||||
| 		ForgeID:       in.ForgeID, | ||||
| 		ForgeRemoteID: model.ForgeRemoteID("0"), // TODO: search for the user in the forge and get the remote id | ||||
| 	} | ||||
| 	if err = user.Validate(); err != nil { | ||||
| @@ -156,10 +189,25 @@ func PostUser(c *gin.Context) { | ||||
| //	@Tags			Users | ||||
| //	@Param			Authorization	header	string	true	"Insert your personal access token"	default(Bearer <personal access token>) | ||||
| //	@Param			login			path	string	true	"the user's login name" | ||||
| //	@Param			forge_id		query	string	true	"specify forge (else default will be used)" | ||||
| //	@Param			forge_remote_id	query	string	false	"specify user id at forge (else fallback to login)" | ||||
| func DeleteUser(c *gin.Context) { | ||||
| 	_store := store.FromContext(c) | ||||
|  | ||||
| 	user, err := _store.GetUserLogin(c.Param("login")) | ||||
| 	forgeID, err := strconv.ParseInt(c.DefaultQuery("forge_id", fmt.Sprint(defaultForgeID)), 10, 64) | ||||
| 	if err != nil { | ||||
| 		c.AbortWithStatus(http.StatusBadRequest) | ||||
| 		return | ||||
| 	} | ||||
| 	forgeRemoteID := model.ForgeRemoteID(c.Query("forge_remote_id")) | ||||
|  | ||||
| 	var user *model.User | ||||
|  | ||||
| 	if forgeRemoteID.IsValid() { | ||||
| 		user, err = store.FromContext(c).GetUserByRemoteID(forgeID, forgeRemoteID) | ||||
| 	} else { | ||||
| 		user, err = store.FromContext(c).GetUserByLogin(forgeID, c.Param("login")) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		handleDBError(c, err) | ||||
| 		return | ||||
|   | ||||
| @@ -45,9 +45,9 @@ func (mode ApprovalMode) Valid() bool { | ||||
| type Repo struct { | ||||
| 	ID      int64 `json:"id,omitempty"                    xorm:"pk autoincr 'id'"` | ||||
| 	UserID  int64 `json:"-"                               xorm:"INDEX 'user_id'"` | ||||
| 	ForgeID int64 `json:"forge_id,omitempty"              xorm:"forge_id"` | ||||
| 	ForgeID int64 `json:"forge_id,omitempty"              xorm:"UNIQUE(forge) forge_id"` | ||||
| 	// ForgeRemoteID is the unique identifier for the repository on the forge. | ||||
| 	ForgeRemoteID                ForgeRemoteID        `json:"forge_remote_id"                 xorm:"forge_remote_id"` | ||||
| 	ForgeRemoteID                ForgeRemoteID        `json:"forge_remote_id"                 xorm:"UNIQUE(forge) forge_remote_id"` | ||||
| 	OrgID                        int64                `json:"org_id"                          xorm:"INDEX 'org_id'"` | ||||
| 	Owner                        string               `json:"owner"                           xorm:"UNIQUE(name) 'owner'"` | ||||
| 	Name                         string               `json:"name"                            xorm:"UNIQUE(name) 'name'"` | ||||
|   | ||||
| @@ -34,9 +34,9 @@ type User struct { | ||||
| 	// required: true | ||||
| 	ID int64 `json:"id" xorm:"pk autoincr 'id'"` | ||||
|  | ||||
| 	ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"` | ||||
| 	ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id UNIQUE(forge)"` | ||||
|  | ||||
| 	ForgeRemoteID ForgeRemoteID `json:"-" xorm:"forge_remote_id"` | ||||
| 	ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id UNIQUE(forge)"` | ||||
|  | ||||
| 	// Login is the username for this user. | ||||
| 	// | ||||
|   | ||||
| @@ -61,9 +61,9 @@ func TestOrgCRUD(t *testing.T) { | ||||
| 	someUser := &model.Org{Name: "some_other_u", IsUser: true} | ||||
| 	assert.NoError(t, store.OrgCreate(someUser)) | ||||
| 	assert.NoError(t, store.OrgCreate(&model.Org{Name: "some_other_org"})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{UserID: 1, Owner: "some_other_u", Name: "abc", FullName: "some_other_u/abc", OrgID: someUser.ID})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{UserID: 1, Owner: "some_other_u", Name: "xyz", FullName: "some_other_u/xyz", OrgID: someUser.ID})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{UserID: 1, Owner: "renamedorg", Name: "567", FullName: "renamedorg/567", OrgID: orgOne.ID})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ForgeRemoteID: "a", UserID: 1, Owner: "some_other_u", Name: "abc", FullName: "some_other_u/abc", OrgID: someUser.ID})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ForgeRemoteID: "b", UserID: 1, Owner: "some_other_u", Name: "xyz", FullName: "some_other_u/xyz", OrgID: someUser.ID})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ForgeRemoteID: "c", UserID: 1, Owner: "renamedorg", Name: "567", FullName: "renamedorg/567", OrgID: orgOne.ID})) | ||||
| 	assert.Error(t, store.OrgCreate(&model.Org{Name: ""}), "expect to fail if name is empty") | ||||
|  | ||||
| 	// get all repos for a specific org | ||||
|   | ||||
| @@ -206,8 +206,8 @@ func TestPipelineIncrement(t *testing.T) { | ||||
| 	store, closer := newTestStore(t, new(model.Pipeline), new(model.Repo)) | ||||
| 	defer closer() | ||||
|  | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ID: 1, Owner: "1", Name: "1", FullName: "1/1"})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ID: 2, Owner: "2", Name: "2", FullName: "2/2"})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ID: 1, Owner: "1", Name: "1", FullName: "1/1", ForgeRemoteID: "1"})) | ||||
| 	assert.NoError(t, store.CreateRepo(&model.Repo{ID: 2, Owner: "2", Name: "2", FullName: "2/2", ForgeRemoteID: "2"})) | ||||
|  | ||||
| 	pipelineA := &model.Pipeline{RepoID: 1} | ||||
| 	if !assert.NoError(t, store.CreatePipeline(pipelineA)) { | ||||
|   | ||||
| @@ -254,22 +254,25 @@ func TestRepoCount(t *testing.T) { | ||||
| 	defer closer() | ||||
|  | ||||
| 	repo1 := &model.Repo{ | ||||
| 		Owner:    "bradrydzewski", | ||||
| 		Name:     "test", | ||||
| 		FullName: "bradrydzewski/test", | ||||
| 		IsActive: true, | ||||
| 		ForgeRemoteID: "A", | ||||
| 		Owner:         "bradrydzewski", | ||||
| 		Name:          "test", | ||||
| 		FullName:      "bradrydzewski/test", | ||||
| 		IsActive:      true, | ||||
| 	} | ||||
| 	repo2 := &model.Repo{ | ||||
| 		Owner:    "test", | ||||
| 		Name:     "test", | ||||
| 		FullName: "test/test", | ||||
| 		IsActive: true, | ||||
| 		ForgeRemoteID: "B", | ||||
| 		Owner:         "test", | ||||
| 		Name:          "test", | ||||
| 		FullName:      "test/test", | ||||
| 		IsActive:      true, | ||||
| 	} | ||||
| 	repo3 := &model.Repo{ | ||||
| 		Owner:    "test", | ||||
| 		Name:     "test-ui", | ||||
| 		FullName: "test/test-ui", | ||||
| 		IsActive: false, | ||||
| 		ForgeRemoteID: "C", | ||||
| 		Owner:         "test", | ||||
| 		Name:          "test-ui", | ||||
| 		FullName:      "test/test-ui", | ||||
| 		IsActive:      false, | ||||
| 	} | ||||
| 	assert.NoError(t, store.CreateRepo(repo1)) | ||||
| 	assert.NoError(t, store.CreateRepo(repo2)) | ||||
| @@ -297,10 +300,12 @@ func TestRepoCrud(t *testing.T) { | ||||
| 	defer closer() | ||||
|  | ||||
| 	repo := model.Repo{ | ||||
| 		UserID:   1, | ||||
| 		FullName: "bradrydzewski/test", | ||||
| 		Owner:    "bradrydzewski", | ||||
| 		Name:     "test", | ||||
| 		ForgeID:       1, | ||||
| 		ForgeRemoteID: "bradrydzewskitest", | ||||
| 		UserID:        1, | ||||
| 		FullName:      "bradrydzewski/test", | ||||
| 		Owner:         "bradrydzewski", | ||||
| 		Name:          "test", | ||||
| 	} | ||||
| 	assert.NoError(t, store.CreateRepo(&repo)) | ||||
| 	pipeline := model.Pipeline{ | ||||
| @@ -313,10 +318,12 @@ func TestRepoCrud(t *testing.T) { | ||||
|  | ||||
| 	// create unrelated | ||||
| 	repoUnrelated := model.Repo{ | ||||
| 		UserID:   2, | ||||
| 		FullName: "x/x", | ||||
| 		Owner:    "x", | ||||
| 		Name:     "x", | ||||
| 		ForgeRemoteID: "xx", | ||||
| 		ForgeID:       1, | ||||
| 		UserID:        2, | ||||
| 		FullName:      "x/x", | ||||
| 		Owner:         "x", | ||||
| 		Name:          "x", | ||||
| 	} | ||||
| 	assert.NoError(t, store.CreateRepo(&repoUnrelated)) | ||||
| 	pipelineUnrelated := model.Pipeline{ | ||||
| @@ -350,6 +357,7 @@ func TestRepoRedirection(t *testing.T) { | ||||
|  | ||||
| 	repo := model.Repo{ | ||||
| 		UserID:        1, | ||||
| 		ForgeID:       1, | ||||
| 		ForgeRemoteID: "1", | ||||
| 		FullName:      "bradrydzewski/test", | ||||
| 		Owner:         "bradrydzewski", | ||||
| @@ -378,10 +386,11 @@ func TestRepoRedirection(t *testing.T) { | ||||
|  | ||||
| 	// test getting repo without forge ID (use name fallback) | ||||
| 	repo = model.Repo{ | ||||
| 		UserID:   1, | ||||
| 		FullName: "bradrydzewski/test-no-forge-id", | ||||
| 		Owner:    "bradrydzewski", | ||||
| 		Name:     "test-no-forge-id", | ||||
| 		UserID:        1, | ||||
| 		ForgeRemoteID: "bradrydzewski/test-no-forge-id", | ||||
| 		FullName:      "bradrydzewski/test-no-forge-id", | ||||
| 		Owner:         "bradrydzewski", | ||||
| 		Name:          "test-no-forge-id", | ||||
| 	} | ||||
| 	assert.NoError(t, store.CreateRepo(&repo)) | ||||
|  | ||||
|   | ||||
| @@ -18,8 +18,6 @@ import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
|  | ||||
| 	"xorm.io/xorm" | ||||
|  | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/model" | ||||
| 	"go.woodpecker-ci.org/woodpecker/v3/server/store/types" | ||||
| ) | ||||
| @@ -29,23 +27,16 @@ func (s storage) GetUser(id int64) (*model.User, error) { | ||||
| 	return user, wrapGet(s.engine.ID(id).Get(user)) | ||||
| } | ||||
|  | ||||
| func (s storage) GetUserRemoteID(remoteID model.ForgeRemoteID, login string) (*model.User, error) { | ||||
| func (s storage) GetUserByRemoteID(forgeID int64, userRemoteID model.ForgeRemoteID) (*model.User, error) { | ||||
| 	sess := s.engine.NewSession() | ||||
| 	user := new(model.User) | ||||
| 	err := wrapGet(sess.Where("forge_remote_id = ?", remoteID).Get(user)) | ||||
| 	if err != nil { | ||||
| 		return s.getUserLogin(sess, login) | ||||
| 	} | ||||
| 	return user, err | ||||
| 	return user, wrapGet(sess.Where("forge_id = ? AND forge_remote_id = ?", forgeID, userRemoteID).Get(user)) | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| func (s storage) GetUserByLogin(forgeID int64, login string) (*model.User, error) { | ||||
| 	sess := s.engine.NewSession() | ||||
| 	user := new(model.User) | ||||
| 	return user, wrapGet(sess.Where("login=?", login).Get(user)) | ||||
| 	return user, wrapGet(sess.Where("forge_id = ? AND login=?", forgeID, login).Get(user)) | ||||
| } | ||||
|  | ||||
| func (s storage) GetUserList(p *model.ListOptions) ([]*model.User, error) { | ||||
|   | ||||
| @@ -32,11 +32,12 @@ func TestUsers(t *testing.T) { | ||||
| 	assert.Zero(t, count) | ||||
|  | ||||
| 	user := model.User{ | ||||
| 		Login:        "joe", | ||||
| 		AccessToken:  "f0b461ca586c27872b43a0685cbc2847", | ||||
| 		RefreshToken: "976f22a5eef7caacb7e678d6c52f49b1", | ||||
| 		Email:        "foo@bar.com", | ||||
| 		Avatar:       "b9015b0857e16ac4d94a0ffd9a0b79c8", | ||||
| 		Login:         "joe", | ||||
| 		ForgeRemoteID: "joe", | ||||
| 		AccessToken:   "f0b461ca586c27872b43a0685cbc2847", | ||||
| 		RefreshToken:  "976f22a5eef7caacb7e678d6c52f49b1", | ||||
| 		Email:         "foo@bar.com", | ||||
| 		Avatar:        "b9015b0857e16ac4d94a0ffd9a0b79c8", | ||||
| 	} | ||||
| 	err = store.CreateUser(&user) | ||||
| 	assert.NoError(t, err) | ||||
| @@ -54,25 +55,27 @@ func TestUsers(t *testing.T) { | ||||
| 	assert.Equal(t, user.Email, getUser.Email) | ||||
| 	assert.Equal(t, user.Avatar, getUser.Avatar) | ||||
|  | ||||
| 	getUser, err = store.GetUserLogin(user.Login) | ||||
| 	getUser, err = store.GetUserByLogin(user.ForgeID, user.Login) | ||||
| 	assert.NoError(t, err) | ||||
| 	assert.Equal(t, user.ID, getUser.ID) | ||||
| 	assert.Equal(t, user.Login, getUser.Login) | ||||
|  | ||||
| 	// check unique login | ||||
| 	user2 := model.User{ | ||||
| 		Login:       "Joe", | ||||
| 		Email:       "foo2@bar.com", | ||||
| 		AccessToken: "ab20g0ddaf012c744e136da16aa21ad9", | ||||
| 		Login:         "Joe", | ||||
| 		ForgeRemoteID: "joe", | ||||
| 		Email:         "foo2@bar.com", | ||||
| 		AccessToken:   "ab20g0ddaf012c744e136da16aa21ad9", | ||||
| 	} | ||||
| 	err2 = store.CreateUser(&user2) | ||||
| 	assert.Error(t, err2) | ||||
|  | ||||
| 	user2 = model.User{ | ||||
| 		Login:       "jane", | ||||
| 		Email:       "foo@bar.com", | ||||
| 		AccessToken: "ab20g0ddaf012c744e136da16aa21ad9", | ||||
| 		Hash:        "A", | ||||
| 		Login:         "jane", | ||||
| 		ForgeRemoteID: "jane", | ||||
| 		Email:         "foo@bar.com", | ||||
| 		AccessToken:   "ab20g0ddaf012c744e136da16aa21ad9", | ||||
| 		Hash:          "A", | ||||
| 	} | ||||
| 	assert.NoError(t, store.CreateUser(&user2)) | ||||
| 	users, err := store.GetUserList(&model.ListOptions{Page: 1, PerPage: 50}) | ||||
| @@ -112,9 +115,10 @@ func TestCreateUserWithExistingOrg(t *testing.T) { | ||||
|  | ||||
| 	// Create a new user with the same name as the existing organization | ||||
| 	newUser := &model.User{ | ||||
| 		Login:   "existingOrg", | ||||
| 		Hash:    "A", | ||||
| 		ForgeID: 1, | ||||
| 		Login:         "existingOrg", | ||||
| 		ForgeRemoteID: "A", | ||||
| 		Hash:          "A", | ||||
| 		ForgeID:       1, | ||||
| 	} | ||||
| 	err = store.CreateUser(newUser) | ||||
| 	assert.NoError(t, err) | ||||
| @@ -124,9 +128,10 @@ func TestCreateUserWithExistingOrg(t *testing.T) { | ||||
| 	assert.Equal(t, "existingOrg", updatedOrg.Name) | ||||
|  | ||||
| 	newUser2 := &model.User{ | ||||
| 		Login:   "new-user", | ||||
| 		ForgeID: 1, | ||||
| 		Hash:    "B", | ||||
| 		Login:         "new-user", | ||||
| 		ForgeRemoteID: "B", | ||||
| 		ForgeID:       1, | ||||
| 		Hash:          "B", | ||||
| 	} | ||||
| 	err = store.CreateUser(newUser2) | ||||
| 	assert.NoError(t, err) | ||||
|   | ||||
| @@ -2712,6 +2712,142 @@ func (_c *MockStore_GetUser_Call) RunAndReturn(run func(n int64) (*model.User, e | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GetUserByLogin provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GetUserByLogin(n int64, s string) (*model.User, error) { | ||||
| 	ret := _mock.Called(n, s) | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for GetUserByLogin") | ||||
| 	} | ||||
|  | ||||
| 	var r0 *model.User | ||||
| 	var r1 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(int64, string) (*model.User, error)); ok { | ||||
| 		return returnFunc(n, s) | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(0).(func(int64, string) *model.User); ok { | ||||
| 		r0 = returnFunc(n, s) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*model.User) | ||||
| 		} | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(1).(func(int64, string) error); ok { | ||||
| 		r1 = returnFunc(n, s) | ||||
| 	} else { | ||||
| 		r1 = ret.Error(1) | ||||
| 	} | ||||
| 	return r0, r1 | ||||
| } | ||||
|  | ||||
| // MockStore_GetUserByLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserByLogin' | ||||
| type MockStore_GetUserByLogin_Call struct { | ||||
| 	*mock.Call | ||||
| } | ||||
|  | ||||
| // GetUserByLogin is a helper method to define mock.On call | ||||
| //   - n int64 | ||||
| //   - s string | ||||
| func (_e *MockStore_Expecter) GetUserByLogin(n interface{}, s interface{}) *MockStore_GetUserByLogin_Call { | ||||
| 	return &MockStore_GetUserByLogin_Call{Call: _e.mock.On("GetUserByLogin", n, s)} | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByLogin_Call) Run(run func(n int64, s string)) *MockStore_GetUserByLogin_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 int64 | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(int64) | ||||
| 		} | ||||
| 		var arg1 string | ||||
| 		if args[1] != nil { | ||||
| 			arg1 = args[1].(string) | ||||
| 		} | ||||
| 		run( | ||||
| 			arg0, | ||||
| 			arg1, | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByLogin_Call) Return(user *model.User, err error) *MockStore_GetUserByLogin_Call { | ||||
| 	_c.Call.Return(user, err) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByLogin_Call) RunAndReturn(run func(n int64, s string) (*model.User, error)) *MockStore_GetUserByLogin_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GetUserByRemoteID provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GetUserByRemoteID(n int64, forgeRemoteID model.ForgeRemoteID) (*model.User, error) { | ||||
| 	ret := _mock.Called(n, forgeRemoteID) | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for GetUserByRemoteID") | ||||
| 	} | ||||
|  | ||||
| 	var r0 *model.User | ||||
| 	var r1 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(int64, model.ForgeRemoteID) (*model.User, error)); ok { | ||||
| 		return returnFunc(n, forgeRemoteID) | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(0).(func(int64, model.ForgeRemoteID) *model.User); ok { | ||||
| 		r0 = returnFunc(n, forgeRemoteID) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*model.User) | ||||
| 		} | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(1).(func(int64, model.ForgeRemoteID) error); ok { | ||||
| 		r1 = returnFunc(n, forgeRemoteID) | ||||
| 	} else { | ||||
| 		r1 = ret.Error(1) | ||||
| 	} | ||||
| 	return r0, r1 | ||||
| } | ||||
|  | ||||
| // MockStore_GetUserByRemoteID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserByRemoteID' | ||||
| type MockStore_GetUserByRemoteID_Call struct { | ||||
| 	*mock.Call | ||||
| } | ||||
|  | ||||
| // GetUserByRemoteID is a helper method to define mock.On call | ||||
| //   - n int64 | ||||
| //   - forgeRemoteID model.ForgeRemoteID | ||||
| func (_e *MockStore_Expecter) GetUserByRemoteID(n interface{}, forgeRemoteID interface{}) *MockStore_GetUserByRemoteID_Call { | ||||
| 	return &MockStore_GetUserByRemoteID_Call{Call: _e.mock.On("GetUserByRemoteID", n, forgeRemoteID)} | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByRemoteID_Call) Run(run func(n int64, forgeRemoteID model.ForgeRemoteID)) *MockStore_GetUserByRemoteID_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 int64 | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(int64) | ||||
| 		} | ||||
| 		var arg1 model.ForgeRemoteID | ||||
| 		if args[1] != nil { | ||||
| 			arg1 = args[1].(model.ForgeRemoteID) | ||||
| 		} | ||||
| 		run( | ||||
| 			arg0, | ||||
| 			arg1, | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByRemoteID_Call) Return(user *model.User, err error) *MockStore_GetUserByRemoteID_Call { | ||||
| 	_c.Call.Return(user, err) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserByRemoteID_Call) RunAndReturn(run func(n int64, forgeRemoteID model.ForgeRemoteID) (*model.User, error)) *MockStore_GetUserByRemoteID_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GetUserCount provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GetUserCount() (int64, error) { | ||||
| 	ret := _mock.Called() | ||||
| @@ -2827,136 +2963,6 @@ func (_c *MockStore_GetUserList_Call) RunAndReturn(run func(p *model.ListOptions | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GetUserLogin provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GetUserLogin(s string) (*model.User, error) { | ||||
| 	ret := _mock.Called(s) | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for GetUserLogin") | ||||
| 	} | ||||
|  | ||||
| 	var r0 *model.User | ||||
| 	var r1 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string) (*model.User, error)); ok { | ||||
| 		return returnFunc(s) | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string) *model.User); ok { | ||||
| 		r0 = returnFunc(s) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*model.User) | ||||
| 		} | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(1).(func(string) error); ok { | ||||
| 		r1 = returnFunc(s) | ||||
| 	} else { | ||||
| 		r1 = ret.Error(1) | ||||
| 	} | ||||
| 	return r0, r1 | ||||
| } | ||||
|  | ||||
| // MockStore_GetUserLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserLogin' | ||||
| type MockStore_GetUserLogin_Call struct { | ||||
| 	*mock.Call | ||||
| } | ||||
|  | ||||
| // GetUserLogin is a helper method to define mock.On call | ||||
| //   - s string | ||||
| func (_e *MockStore_Expecter) GetUserLogin(s interface{}) *MockStore_GetUserLogin_Call { | ||||
| 	return &MockStore_GetUserLogin_Call{Call: _e.mock.On("GetUserLogin", s)} | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserLogin_Call) Run(run func(s string)) *MockStore_GetUserLogin_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 string | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(string) | ||||
| 		} | ||||
| 		run( | ||||
| 			arg0, | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserLogin_Call) Return(user *model.User, err error) *MockStore_GetUserLogin_Call { | ||||
| 	_c.Call.Return(user, err) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserLogin_Call) RunAndReturn(run func(s string) (*model.User, error)) *MockStore_GetUserLogin_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GetUserRemoteID provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GetUserRemoteID(forgeRemoteID model.ForgeRemoteID, s string) (*model.User, error) { | ||||
| 	ret := _mock.Called(forgeRemoteID, s) | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for GetUserRemoteID") | ||||
| 	} | ||||
|  | ||||
| 	var r0 *model.User | ||||
| 	var r1 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(model.ForgeRemoteID, string) (*model.User, error)); ok { | ||||
| 		return returnFunc(forgeRemoteID, s) | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(0).(func(model.ForgeRemoteID, string) *model.User); ok { | ||||
| 		r0 = returnFunc(forgeRemoteID, s) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*model.User) | ||||
| 		} | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(1).(func(model.ForgeRemoteID, string) error); ok { | ||||
| 		r1 = returnFunc(forgeRemoteID, s) | ||||
| 	} else { | ||||
| 		r1 = ret.Error(1) | ||||
| 	} | ||||
| 	return r0, r1 | ||||
| } | ||||
|  | ||||
| // MockStore_GetUserRemoteID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUserRemoteID' | ||||
| type MockStore_GetUserRemoteID_Call struct { | ||||
| 	*mock.Call | ||||
| } | ||||
|  | ||||
| // GetUserRemoteID is a helper method to define mock.On call | ||||
| //   - forgeRemoteID model.ForgeRemoteID | ||||
| //   - s string | ||||
| func (_e *MockStore_Expecter) GetUserRemoteID(forgeRemoteID interface{}, s interface{}) *MockStore_GetUserRemoteID_Call { | ||||
| 	return &MockStore_GetUserRemoteID_Call{Call: _e.mock.On("GetUserRemoteID", forgeRemoteID, s)} | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserRemoteID_Call) Run(run func(forgeRemoteID model.ForgeRemoteID, s string)) *MockStore_GetUserRemoteID_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 model.ForgeRemoteID | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(model.ForgeRemoteID) | ||||
| 		} | ||||
| 		var arg1 string | ||||
| 		if args[1] != nil { | ||||
| 			arg1 = args[1].(string) | ||||
| 		} | ||||
| 		run( | ||||
| 			arg0, | ||||
| 			arg1, | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserRemoteID_Call) Return(user *model.User, err error) *MockStore_GetUserRemoteID_Call { | ||||
| 	_c.Call.Return(user, err) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockStore_GetUserRemoteID_Call) RunAndReturn(run func(forgeRemoteID model.ForgeRemoteID, s string) (*model.User, error)) *MockStore_GetUserRemoteID_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // GlobalRegistryFind provides a mock function for the type MockStore | ||||
| func (_mock *MockStore) GlobalRegistryFind(s string) (*model.Registry, error) { | ||||
| 	ret := _mock.Called(s) | ||||
|   | ||||
| @@ -26,10 +26,10 @@ 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) | ||||
| 	// GetUserByRemoteID gets a user by remote ID. | ||||
| 	GetUserByRemoteID(int64, model.ForgeRemoteID) (*model.User, error) | ||||
| 	// GetUserByLogin gets a user by its login name. | ||||
| 	GetUserByLogin(int64, string) (*model.User, error) | ||||
| 	// GetUserList gets a list of all users in the system. | ||||
| 	GetUserList(p *model.ListOptions) ([]*model.User, error) | ||||
| 	// GetUserCount gets a count of all users in the system. | ||||
|   | ||||
| @@ -20,6 +20,8 @@ import type { | ||||
|   User, | ||||
| } from './types'; | ||||
|  | ||||
| const DEFAULT_FORGE_ID = 1; | ||||
|  | ||||
| interface RepoListOptions { | ||||
|   all?: boolean; | ||||
| } | ||||
| @@ -386,8 +388,9 @@ export default class WoodpeckerClient extends ApiClient { | ||||
|     return this._get(`/api/users?${query}`) as Promise<User[] | null>; | ||||
|   } | ||||
|  | ||||
|   async getUser(username: string): Promise<User> { | ||||
|     return this._get(`/api/users/${username}`) as Promise<User>; | ||||
|   async getUser(username: string, forgeID?: number): Promise<User> { | ||||
|     const forge = forgeID ?? DEFAULT_FORGE_ID; | ||||
|     return this._get(`/api/users/${username}?forge_id=${forge}`) as Promise<User>; | ||||
|   } | ||||
|  | ||||
|   async createUser(user: Partial<User>): Promise<User> { | ||||
| @@ -399,7 +402,7 @@ export default class WoodpeckerClient extends ApiClient { | ||||
|   } | ||||
|  | ||||
|   async deleteUser(user: User): Promise<unknown> { | ||||
|     return this._delete(`/api/users/${user.login}`); | ||||
|     return this._delete(`/api/users/${user.login}?forge_id=${user.forge_id}`); | ||||
|   } | ||||
|  | ||||
|   async resetToken(): Promise<string> { | ||||
|   | ||||
| @@ -3,6 +3,12 @@ export interface User { | ||||
|   id: number; | ||||
|   // The unique identifier for the account. | ||||
|  | ||||
|   forge_id: number; | ||||
|   // The unique identifier of the forge the account belongs to. | ||||
|  | ||||
|   forge_remote_id: string; | ||||
|   // The unique identifier of user at the remote forge. | ||||
|  | ||||
|   login: string; | ||||
|   // The login name for the account. | ||||
|  | ||||
|   | ||||
| @@ -60,3 +60,5 @@ const ( | ||||
| 	StepTypeCommands StepType = "commands" | ||||
| 	StepTypeCache    StepType = "cache" | ||||
| ) | ||||
|  | ||||
| const defaultForgeID = 1 | ||||
|   | ||||
| @@ -30,7 +30,8 @@ type Client interface { | ||||
| 	Self() (*User, error) | ||||
|  | ||||
| 	// User returns a user by login. | ||||
| 	User(string) (*User, error) | ||||
| 	// It is recommended to specify forgeID (default is 1). | ||||
| 	User(login string, forgeID ...int64) (*User, error) | ||||
|  | ||||
| 	// UserList returns a list of all registered users. | ||||
| 	UserList(opt UserListOptions) ([]*User, error) | ||||
| @@ -42,7 +43,8 @@ type Client interface { | ||||
| 	UserPatch(*User) (*User, error) | ||||
|  | ||||
| 	// UserDel deletes a user account. | ||||
| 	UserDel(string) error | ||||
| 	// It is recommended to specify forgeID (default is 1). | ||||
| 	UserDel(login string, forgeID ...int64) error | ||||
|  | ||||
| 	// Repo returns a repository by name. | ||||
| 	Repo(repoID int64) (*Repo, error) | ||||
|   | ||||
| @@ -4653,8 +4653,14 @@ func (_c *MockClient_StepLogsPurge_Call) RunAndReturn(run func(repoID int64, pip | ||||
| } | ||||
|  | ||||
| // User provides a mock function for the type MockClient | ||||
| func (_mock *MockClient) User(s string) (*woodpecker.User, error) { | ||||
| 	ret := _mock.Called(s) | ||||
| func (_mock *MockClient) User(login string, forgeID ...int64) (*woodpecker.User, error) { | ||||
| 	var tmpRet mock.Arguments | ||||
| 	if len(forgeID) > 0 { | ||||
| 		tmpRet = _mock.Called(login, forgeID) | ||||
| 	} else { | ||||
| 		tmpRet = _mock.Called(login) | ||||
| 	} | ||||
| 	ret := tmpRet | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for User") | ||||
| @@ -4662,18 +4668,18 @@ func (_mock *MockClient) User(s string) (*woodpecker.User, error) { | ||||
|  | ||||
| 	var r0 *woodpecker.User | ||||
| 	var r1 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string) (*woodpecker.User, error)); ok { | ||||
| 		return returnFunc(s) | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string, ...int64) (*woodpecker.User, error)); ok { | ||||
| 		return returnFunc(login, forgeID...) | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string) *woodpecker.User); ok { | ||||
| 		r0 = returnFunc(s) | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string, ...int64) *woodpecker.User); ok { | ||||
| 		r0 = returnFunc(login, forgeID...) | ||||
| 	} else { | ||||
| 		if ret.Get(0) != nil { | ||||
| 			r0 = ret.Get(0).(*woodpecker.User) | ||||
| 		} | ||||
| 	} | ||||
| 	if returnFunc, ok := ret.Get(1).(func(string) error); ok { | ||||
| 		r1 = returnFunc(s) | ||||
| 	if returnFunc, ok := ret.Get(1).(func(string, ...int64) error); ok { | ||||
| 		r1 = returnFunc(login, forgeID...) | ||||
| 	} else { | ||||
| 		r1 = ret.Error(1) | ||||
| 	} | ||||
| @@ -4686,19 +4692,28 @@ type MockClient_User_Call struct { | ||||
| } | ||||
|  | ||||
| // User is a helper method to define mock.On call | ||||
| //   - s string | ||||
| func (_e *MockClient_Expecter) User(s interface{}) *MockClient_User_Call { | ||||
| 	return &MockClient_User_Call{Call: _e.mock.On("User", s)} | ||||
| //   - login string | ||||
| //   - forgeID ...int64 | ||||
| func (_e *MockClient_Expecter) User(login interface{}, forgeID ...interface{}) *MockClient_User_Call { | ||||
| 	return &MockClient_User_Call{Call: _e.mock.On("User", | ||||
| 		append([]interface{}{login}, forgeID...)...)} | ||||
| } | ||||
|  | ||||
| func (_c *MockClient_User_Call) Run(run func(s string)) *MockClient_User_Call { | ||||
| func (_c *MockClient_User_Call) Run(run func(login string, forgeID ...int64)) *MockClient_User_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 string | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(string) | ||||
| 		} | ||||
| 		var arg1 []int64 | ||||
| 		var variadicArgs []int64 | ||||
| 		if len(args) > 1 { | ||||
| 			variadicArgs = args[1].([]int64) | ||||
| 		} | ||||
| 		arg1 = variadicArgs | ||||
| 		run( | ||||
| 			arg0, | ||||
| 			arg1..., | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| @@ -4709,22 +4724,28 @@ func (_c *MockClient_User_Call) Return(user *woodpecker.User, err error) *MockCl | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockClient_User_Call) RunAndReturn(run func(s string) (*woodpecker.User, error)) *MockClient_User_Call { | ||||
| func (_c *MockClient_User_Call) RunAndReturn(run func(login string, forgeID ...int64) (*woodpecker.User, error)) *MockClient_User_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| // UserDel provides a mock function for the type MockClient | ||||
| func (_mock *MockClient) UserDel(s string) error { | ||||
| 	ret := _mock.Called(s) | ||||
| func (_mock *MockClient) UserDel(login string, forgeID ...int64) error { | ||||
| 	var tmpRet mock.Arguments | ||||
| 	if len(forgeID) > 0 { | ||||
| 		tmpRet = _mock.Called(login, forgeID) | ||||
| 	} else { | ||||
| 		tmpRet = _mock.Called(login) | ||||
| 	} | ||||
| 	ret := tmpRet | ||||
|  | ||||
| 	if len(ret) == 0 { | ||||
| 		panic("no return value specified for UserDel") | ||||
| 	} | ||||
|  | ||||
| 	var r0 error | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string) error); ok { | ||||
| 		r0 = returnFunc(s) | ||||
| 	if returnFunc, ok := ret.Get(0).(func(string, ...int64) error); ok { | ||||
| 		r0 = returnFunc(login, forgeID...) | ||||
| 	} else { | ||||
| 		r0 = ret.Error(0) | ||||
| 	} | ||||
| @@ -4737,19 +4758,28 @@ type MockClient_UserDel_Call struct { | ||||
| } | ||||
|  | ||||
| // UserDel is a helper method to define mock.On call | ||||
| //   - s string | ||||
| func (_e *MockClient_Expecter) UserDel(s interface{}) *MockClient_UserDel_Call { | ||||
| 	return &MockClient_UserDel_Call{Call: _e.mock.On("UserDel", s)} | ||||
| //   - login string | ||||
| //   - forgeID ...int64 | ||||
| func (_e *MockClient_Expecter) UserDel(login interface{}, forgeID ...interface{}) *MockClient_UserDel_Call { | ||||
| 	return &MockClient_UserDel_Call{Call: _e.mock.On("UserDel", | ||||
| 		append([]interface{}{login}, forgeID...)...)} | ||||
| } | ||||
|  | ||||
| func (_c *MockClient_UserDel_Call) Run(run func(s string)) *MockClient_UserDel_Call { | ||||
| func (_c *MockClient_UserDel_Call) Run(run func(login string, forgeID ...int64)) *MockClient_UserDel_Call { | ||||
| 	_c.Call.Run(func(args mock.Arguments) { | ||||
| 		var arg0 string | ||||
| 		if args[0] != nil { | ||||
| 			arg0 = args[0].(string) | ||||
| 		} | ||||
| 		var arg1 []int64 | ||||
| 		var variadicArgs []int64 | ||||
| 		if len(args) > 1 { | ||||
| 			variadicArgs = args[1].([]int64) | ||||
| 		} | ||||
| 		arg1 = variadicArgs | ||||
| 		run( | ||||
| 			arg0, | ||||
| 			arg1..., | ||||
| 		) | ||||
| 	}) | ||||
| 	return _c | ||||
| @@ -4760,7 +4790,7 @@ func (_c *MockClient_UserDel_Call) Return(err error) *MockClient_UserDel_Call { | ||||
| 	return _c | ||||
| } | ||||
|  | ||||
| func (_c *MockClient_UserDel_Call) RunAndReturn(run func(s string) error) *MockClient_UserDel_Call { | ||||
| func (_c *MockClient_UserDel_Call) RunAndReturn(run func(login string, forgeID ...int64) error) *MockClient_UserDel_Call { | ||||
| 	_c.Call.Return(run) | ||||
| 	return _c | ||||
| } | ||||
|   | ||||
| @@ -38,12 +38,14 @@ func (mode ApprovalMode) Valid() bool { | ||||
| type ( | ||||
| 	// User represents a user account. | ||||
| 	User struct { | ||||
| 		ID     int64  `json:"id"` | ||||
| 		Login  string `json:"login"` | ||||
| 		Email  string `json:"email"` | ||||
| 		Avatar string `json:"avatar_url"` | ||||
| 		Active bool   `json:"active"` | ||||
| 		Admin  bool   `json:"admin"` | ||||
| 		ID            int64  `json:"id"` | ||||
| 		ForgeID       int64  `json:"forge_id"` | ||||
| 		ForgeRemoteID string `json:"forge_remote_id"` | ||||
| 		Login         string `json:"login"` | ||||
| 		Email         string `json:"email"` | ||||
| 		Avatar        string `json:"avatar_url"` | ||||
| 		Active        bool   `json:"active"` | ||||
| 		Admin         bool   `json:"admin"` | ||||
| 	} | ||||
|  | ||||
| 	TrustedConfiguration struct { | ||||
|   | ||||
| @@ -9,7 +9,7 @@ const ( | ||||
| 	pathSelf  = "%s/api/user" | ||||
| 	pathRepos = "%s/api/user/repos" | ||||
| 	pathUsers = "%s/api/users" | ||||
| 	pathUser  = "%s/api/users/%s" | ||||
| 	pathUser  = "%s/api/users/%s?forge_id=%d" | ||||
| ) | ||||
|  | ||||
| type RepoListOptions struct { | ||||
| @@ -38,10 +38,13 @@ func (c *client) Self() (*User, error) { | ||||
| } | ||||
|  | ||||
| // User returns a user by login. | ||||
| func (c *client) User(login string) (*User, error) { | ||||
| // It is recommended to specify forgeID (default is 1). | ||||
| func (c *client) User(login string, forgeID ...int64) (*User, error) { | ||||
| 	out := new(User) | ||||
| 	uri := fmt.Sprintf(pathUser, c.addr, login) | ||||
| 	err := c.get(uri, out) | ||||
| 	if len(forgeID) == 0 { | ||||
| 		forgeID = []int64{defaultForgeID} | ||||
| 	} | ||||
| 	err := c.get(fmt.Sprintf(pathUser, c.addr, login, forgeID[0]), out) | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| @@ -64,17 +67,22 @@ func (c *client) UserPost(in *User) (*User, error) { | ||||
|  | ||||
| // UserPatch updates a user account. | ||||
| func (c *client) UserPatch(in *User) (*User, error) { | ||||
| 	if in.ForgeID < defaultForgeID { | ||||
| 		in.ForgeID = defaultForgeID | ||||
| 	} | ||||
| 	out := new(User) | ||||
| 	uri := fmt.Sprintf(pathUser, c.addr, in.Login) | ||||
| 	uri := fmt.Sprintf(pathUser, c.addr, in.Login, in.ForgeID) | ||||
| 	err := c.patch(uri, in, out) | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| // UserDel deletes a user account. | ||||
| func (c *client) UserDel(login string) error { | ||||
| 	uri := fmt.Sprintf(pathUser, c.addr, login) | ||||
| 	err := c.delete(uri) | ||||
| 	return err | ||||
| // It is recommended to specify forgeID (default is 1). | ||||
| func (c *client) UserDel(login string, forgeID ...int64) error { | ||||
| 	if len(forgeID) == 0 { | ||||
| 		forgeID = []int64{defaultForgeID} | ||||
| 	} | ||||
| 	return c.delete(fmt.Sprintf(pathUser, c.addr, login, forgeID[0])) | ||||
| } | ||||
|  | ||||
| // RepoList returns a list of all repositories to which | ||||
|   | ||||
		Reference in New Issue
	
	Block a user