From 20e27a9e194f183b15ec3506d1a07cbae29fd343 Mon Sep 17 00:00:00 2001 From: Kemal Zebari Date: Sun, 30 Mar 2025 19:45:12 -0700 Subject: [PATCH 01/12] Add --with-scopes CLI flag when creating a user with access token --- cmd/admin_user_create.go | 18 ++++++++++++++++++ cmd/admin_user_create_test.go | 30 ++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 5e03d6ca3f6df..349d541808ce2 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -66,11 +66,22 @@ var microcmdUserCreate = &cli.Command{ Name: "access-token", Usage: "Generate access token for the user", }, + &cli.StringFlag{ + Name: "with-scopes", + Usage: "Comma separated list of scopes to apply to access token (--access-token is required)", + Value: "", + }, &cli.BoolFlag{ Name: "restricted", Usage: "Make a restricted user account", }, }, + Before: func(c *cli.Context) error { + if c.String("with-scopes") != "" && !c.Bool("access-token") { + return errors.New("--access-token is required when using --with-scopes") + } + return nil + }, } func runCreateUser(c *cli.Context) error { @@ -197,6 +208,13 @@ func runCreateUser(c *cli.Context) error { UID: u.ID, } + // include access token scopes + accessTokenScope, err := auth_model.AccessTokenScope(c.String("with-scopes")).Normalize() + if err != nil { + return fmt.Errorf("invalid access token scope provided: %w", err) + } + t.Scope = accessTokenScope + if err := auth_model.NewAccessToken(ctx, t); err != nil { return err } diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d8044e8de7ee3..2a70488fbf938 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -48,11 +49,11 @@ func TestAdminUserCreate(t *testing.T) { assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false")) }) - t.Run("UserType", func(t *testing.T) { - createUser := func(name, args string) error { - return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args))) - } + createUser := func(name, args string) error { + return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args))) + } + t.Run("UserType", func(t *testing.T) { reset() assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type") assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users") @@ -63,4 +64,25 @@ func TestAdminUserCreate(t *testing.T) { assert.Equal(t, user_model.UserTypeBot, u.Type) assert.Equal(t, "", u.Passwd) }) + + t.Run("AccessToken", func(t *testing.T) { + resetIncluingTokens := func() { + reset() + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) + } + + reset() + assert.NoError(t, createUser("u", "--random-password --access-token")) + a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) + assert.Empty(t, string(a.Scope)) + + resetIncluingTokens() + assert.ErrorContains(t, createUser("u", "--random-password --with-scopes all"), "--access-token is required when using --with-scopes") + + assert.NoError(t, createUser("u", "--random-password --access-token --with-scopes read:issue,read:user")) + a = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) + hasScopes, err := a.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) + assert.NoError(t, err) + assert.True(t, hasScopes) + }) } From 87eec64434c17adff919591b91e9f7a474f824e5 Mon Sep 17 00:00:00 2001 From: Kemal Zebari Date: Sun, 30 Mar 2025 19:55:08 -0700 Subject: [PATCH 02/12] Fix typo in test function --- cmd/admin_user_create_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 2a70488fbf938..eca8493b7aad0 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -66,7 +66,7 @@ func TestAdminUserCreate(t *testing.T) { }) t.Run("AccessToken", func(t *testing.T) { - resetIncluingTokens := func() { + resetIncludingTokens := func() { reset() require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) } @@ -76,7 +76,7 @@ func TestAdminUserCreate(t *testing.T) { a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) assert.Empty(t, string(a.Scope)) - resetIncluingTokens() + resetIncludingTokens() assert.ErrorContains(t, createUser("u", "--random-password --with-scopes all"), "--access-token is required when using --with-scopes") assert.NoError(t, createUser("u", "--random-password --access-token --with-scopes read:issue,read:user")) From fda7e9bed0f18c95927d6c755a2e0e948d65c346 Mon Sep 17 00:00:00 2001 From: Kemal Zebari Date: Sun, 30 Mar 2025 22:28:43 -0700 Subject: [PATCH 03/12] Reimplement --access-token to accept scopes --- cmd/admin_user_create.go | 20 +++++--------------- cmd/admin_user_create_test.go | 16 ++++------------ 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 349d541808ce2..49f2c4e780cb5 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -62,13 +62,9 @@ var microcmdUserCreate = &cli.Command{ Usage: "Length of the random password to be generated", Value: 12, }, - &cli.BoolFlag{ - Name: "access-token", - Usage: "Generate access token for the user", - }, &cli.StringFlag{ - Name: "with-scopes", - Usage: "Comma separated list of scopes to apply to access token (--access-token is required)", + Name: "access-token", + Usage: "Generate an access token for the user, passsing a comma separated list of scopes to apply to it", Value: "", }, &cli.BoolFlag{ @@ -76,12 +72,6 @@ var microcmdUserCreate = &cli.Command{ Usage: "Make a restricted user account", }, }, - Before: func(c *cli.Context) error { - if c.String("with-scopes") != "" && !c.Bool("access-token") { - return errors.New("--access-token is required when using --with-scopes") - } - return nil - }, } func runCreateUser(c *cli.Context) error { @@ -202,14 +192,14 @@ func runCreateUser(c *cli.Context) error { return fmt.Errorf("CreateUser: %w", err) } - if c.Bool("access-token") { + if c.IsSet("access-token") { t := &auth_model.AccessToken{ Name: "gitea-admin", UID: u.ID, } - // include access token scopes - accessTokenScope, err := auth_model.AccessTokenScope(c.String("with-scopes")).Normalize() + // include access token's scopes + accessTokenScope, err := auth_model.AccessTokenScope(c.String("access-token")).Normalize() if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index eca8493b7aad0..d72381ee7ea0c 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -66,21 +66,13 @@ func TestAdminUserCreate(t *testing.T) { }) t.Run("AccessToken", func(t *testing.T) { - resetIncludingTokens := func() { - reset() - require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) - } + reset() + assert.NoError(t, createUser("u", "--random-password")) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) reset() - assert.NoError(t, createUser("u", "--random-password --access-token")) + assert.NoError(t, createUser("u", "--random-password --access-token read:issue,read:user")) a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) - assert.Empty(t, string(a.Scope)) - - resetIncludingTokens() - assert.ErrorContains(t, createUser("u", "--random-password --with-scopes all"), "--access-token is required when using --with-scopes") - - assert.NoError(t, createUser("u", "--random-password --access-token --with-scopes read:issue,read:user")) - a = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) hasScopes, err := a.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) assert.NoError(t, err) assert.True(t, hasScopes) From 3e0610055593819dcb97b1db4530f19c6f4b97de Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 31 Mar 2025 14:05:56 +0800 Subject: [PATCH 04/12] improve usage --- cmd/admin_user_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 49f2c4e780cb5..2d9fd00acfe60 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -64,7 +64,7 @@ var microcmdUserCreate = &cli.Command{ }, &cli.StringFlag{ Name: "access-token", - Usage: "Generate an access token for the user, passsing a comma separated list of scopes to apply to it", + Usage: `Generate an access token for the user, passing a comma separated list of scopes to apply to it, examples: "all", "public-only,read:issue", "write:repository,write:user"`, Value: "", }, &cli.BoolFlag{ From 61d0a2f4d3227bb6c841769573d46514fece1b1f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 31 Mar 2025 14:31:35 +0800 Subject: [PATCH 05/12] use separate cli flags --- cmd/admin_user_create.go | 21 ++++++++++++++++----- cmd/admin_user_create_test.go | 4 ++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 2d9fd00acfe60..8120c9a262288 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -62,10 +62,19 @@ var microcmdUserCreate = &cli.Command{ Usage: "Length of the random password to be generated", Value: 12, }, - &cli.StringFlag{ + &cli.BoolFlag{ Name: "access-token", - Usage: `Generate an access token for the user, passing a comma separated list of scopes to apply to it, examples: "all", "public-only,read:issue", "write:repository,write:user"`, - Value: "", + Usage: "Generate access token for the user", + }, + &cli.StringFlag{ + Name: "access-token-name", + Usage: `Name of the generated access token`, + Value: "gitea-admin", + }, + &cli.StringFlag{ + Name: "access-token-scopes", + Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, + Value: "all", }, &cli.BoolFlag{ Name: "restricted", @@ -194,12 +203,12 @@ func runCreateUser(c *cli.Context) error { if c.IsSet("access-token") { t := &auth_model.AccessToken{ - Name: "gitea-admin", + Name: c.String("access-token-name"), UID: u.ID, } // include access token's scopes - accessTokenScope, err := auth_model.AccessTokenScope(c.String("access-token")).Normalize() + accessTokenScope, err := auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize() if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } @@ -210,6 +219,8 @@ func runCreateUser(c *cli.Context) error { } fmt.Printf("Access token was successfully created... %s\n", t.Token) + } else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") { + return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set") } fmt.Printf("New user '%s' has been successfully created!\n", username) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d72381ee7ea0c..8cac2710324d5 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -71,8 +71,8 @@ func TestAdminUserCreate(t *testing.T) { assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) reset() - assert.NoError(t, createUser("u", "--random-password --access-token read:issue,read:user")) - a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) + assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user")) + a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) hasScopes, err := a.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) assert.NoError(t, err) assert.True(t, hasScopes) From 010962cfae420117817a852a17e5937eadaa7af8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 31 Mar 2025 14:42:28 +0800 Subject: [PATCH 06/12] fix tests --- cmd/admin_user_create_test.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 8cac2710324d5..e6c6d04564360 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -23,6 +23,7 @@ func TestAdminUserCreate(t *testing.T) { reset := func() { require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) } t.Run("MustChangePassword", func(t *testing.T) { @@ -66,15 +67,30 @@ func TestAdminUserCreate(t *testing.T) { }) t.Run("AccessToken", func(t *testing.T) { + // no generated access token reset() assert.NoError(t, createUser("u", "--random-password")) assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) + // using "--access-token" only means "all" access + reset() + assert.NoError(t, createUser("u", "--random-password --access-token")) + assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) + accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) + hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) + assert.NoError(t, err) + assert.True(t, hasScopes) + + // using "--access-token" with name & scopes reset() assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user")) - a := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) - hasScopes, err := a.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) + assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) + accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) + hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) assert.NoError(t, err) assert.True(t, hasScopes) + hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) + assert.NoError(t, err) + assert.False(t, hasScopes) }) } From c02b06efb39fd04ef7c8ab01d36def488a01ee5f Mon Sep 17 00:00:00 2001 From: Kemal Zebari Date: Mon, 31 Mar 2025 19:47:56 -0700 Subject: [PATCH 07/12] Add additional tests --- cmd/admin_user_create_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index e6c6d04564360..9555d33801c82 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -92,5 +92,12 @@ func TestAdminUserCreate(t *testing.T) { hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) assert.NoError(t, err) assert.False(t, hasScopes) + + // using "--access-token-name" without "--access-token" + reset() + assert.ErrorContains(t, createUser("u", "--random-password --access-token-name new-token-name"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + + // using "--access-token-scopes" without "--access-token" + assert.ErrorContains(t, createUser("u", "--random-password --access-token-scopes read:issue"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") }) } From ae5f92ccd7cce193bac13f8ef6032f23018d470a Mon Sep 17 00:00:00 2001 From: Kemal Zebari Date: Mon, 31 Mar 2025 20:02:23 -0700 Subject: [PATCH 08/12] Add missing reset() --- cmd/admin_user_create_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 9555d33801c82..4582efff79ada 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -98,6 +98,7 @@ func TestAdminUserCreate(t *testing.T) { assert.ErrorContains(t, createUser("u", "--random-password --access-token-name new-token-name"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") // using "--access-token-scopes" without "--access-token" + reset() assert.ErrorContains(t, createUser("u", "--random-password --access-token-scopes read:issue"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") }) } From 0387ff2cee4460b03471985d763fbbd3cad26986 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 1 Apr 2025 14:53:13 +0800 Subject: [PATCH 09/12] handle empty scope --- cmd/admin_user_create.go | 3 +++ cmd/admin_user_create_test.go | 11 +++++++++-- cmd/admin_user_generate_access_token.go | 9 ++++++--- models/auth/access_token_scope.go | 4 ++++ routers/web/user/setting/applications.go | 2 +- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 8120c9a262288..a2614737396a4 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -212,6 +212,9 @@ func runCreateUser(c *cli.Context) error { if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } + if !accessTokenScope.HasPermissionScope() { + return errors.New("access token does not have any permission") + } t.Scope = accessTokenScope if err := auth_model.NewAccessToken(ctx, t); err != nil { diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index a25a30786dd01..2c75270c4fd8a 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -95,10 +95,17 @@ func TestAdminUserCreate(t *testing.T) { // using "--access-token-name" without "--access-token" reset() - assert.ErrorContains(t, createUser("u", "--random-password --access-token-name new-token-name"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + err = createUser("u", "--random-password --access-token-name new-token-name") + assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") // using "--access-token-scopes" without "--access-token" reset() - assert.ErrorContains(t, createUser("u", "--random-password --access-token-scopes read:issue"), "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + err = createUser("u", "--random-password --access-token-scopes read:issue") + assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + + // empty permission + reset() + err = createUser("u", "--random-password --access-token --access-token-scopes public-only") + assert.ErrorContains(t, err, "access token does not have any permission") }) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 6c2c10494ee5f..f6db7a74bd1ec 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{ }, &cli.StringFlag{ Name: "scopes", - Value: "", - Usage: "Comma separated list of scopes to apply to access token", + Value: "all", + Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, }, }, Action: runGenerateAccessToken, @@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{ func runGenerateAccessToken(c *cli.Context) error { if !c.IsSet("username") { - return errors.New("You must provide a username to generate a token for") + return errors.New("you must provide a username to generate a token for") } ctx, cancel := installSignals() @@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error { if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } + if !accessTokenScope.HasPermissionScope() { + return errors.New("access token does not have any permission") + } t.Scope = accessTokenScope // create the token diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 0e5b2e96e6602..2293fd89a02e1 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -295,6 +295,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) { return bitmap.toScope(), nil } +func (s AccessTokenScope) HasPermissionScope() bool { + return s != "" && s != AccessTokenScopePublicOnly +} + // PublicOnly checks if this token scope is limited to public resources func (s AccessTokenScope) PublicOnly() (bool, error) { bitmap, err := s.parse() diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 1f6c97a5cc681..c3d8b93adbd07 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -54,7 +54,7 @@ func ApplicationsPost(ctx *context.Context) { ctx.ServerError("GetScope", err) return } - if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { + if !scope.HasPermissionScope() { ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) } From ac86171e2ffe13802900a58c97fae3522aac769e Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 1 Apr 2025 15:02:05 +0800 Subject: [PATCH 10/12] arguments should be prepared before creating the user & access token, in case there is anything wrong --- cmd/admin_user_create.go | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index a2614737396a4..e47f508948c5b 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" @@ -197,33 +198,38 @@ func runCreateUser(c *cli.Context) error { IsRestricted: restricted, } - if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { - return fmt.Errorf("CreateUser: %w", err) - } - + var accessTokenName string + var accessTokenScope auth_model.AccessTokenScope if c.IsSet("access-token") { - t := &auth_model.AccessToken{ - Name: c.String("access-token-name"), - UID: u.ID, + accessTokenName = strings.TrimSpace(c.String("access-token-name")) + if accessTokenName == "" { + return errors.New("access-token-name cannot be empty") } - - // include access token's scopes - accessTokenScope, err := auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize() + var err error + accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize() if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } if !accessTokenScope.HasPermissionScope() { return errors.New("access token does not have any permission") } - t.Scope = accessTokenScope + } else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") { + return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set") + } + + // arguments should be prepared before creating the user & access token, in case there is anything wrong + // create the user + if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { + return fmt.Errorf("CreateUser: %w", err) + } + // create the access token + if accessTokenScope != "" { + t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} if err := auth_model.NewAccessToken(ctx, t); err != nil { return err } - fmt.Printf("Access token was successfully created... %s\n", t.Token) - } else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") { - return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set") } fmt.Printf("New user '%s' has been successfully created!\n", username) From 2c768f5d9f01e6010d0cd04b824ad566e3e71ad1 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 1 Apr 2025 17:44:37 +0800 Subject: [PATCH 11/12] add more test asserts --- cmd/admin_user_create_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index 2c75270c4fd8a..6fd6f8c22635c 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -70,11 +70,13 @@ func TestAdminUserCreate(t *testing.T) { // no generated access token reset() assert.NoError(t, createUser("u", "--random-password")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) // using "--access-token" only means "all" access reset() assert.NoError(t, createUser("u", "--random-password --access-token")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) @@ -84,6 +86,7 @@ func TestAdminUserCreate(t *testing.T) { // using "--access-token" with name & scopes reset() assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) @@ -96,16 +99,22 @@ func TestAdminUserCreate(t *testing.T) { // using "--access-token-name" without "--access-token" reset() err = createUser("u", "--random-password --access-token-name new-token-name") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") // using "--access-token-scopes" without "--access-token" reset() err = createUser("u", "--random-password --access-token-scopes read:issue") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") // empty permission reset() err = createUser("u", "--random-password --access-token --access-token-scopes public-only") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) assert.ErrorContains(t, err, "access token does not have any permission") }) } From 6f5c97c59a5ee7a8de20216a26c4fa015078d098 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Wed, 2 Apr 2025 13:54:40 +0800 Subject: [PATCH 12/12] fix prompt order --- cmd/admin_user_create.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index e47f508948c5b..ebe0266d1f2c1 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -223,6 +223,8 @@ func runCreateUser(c *cli.Context) error { if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { return fmt.Errorf("CreateUser: %w", err) } + fmt.Printf("New user '%s' has been successfully created!\n", username) + // create the access token if accessTokenScope != "" { t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} @@ -231,7 +233,5 @@ func runCreateUser(c *cli.Context) error { } fmt.Printf("Access token was successfully created... %s\n", t.Token) } - - fmt.Printf("New user '%s' has been successfully created!\n", username) return nil }