mirror of https://github.com/go-gitea/gitea.git
Add new CLI flags to set name and scopes when creating a user with access token (#34080)
Resolves #33474. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>pull/32669/merge
parent
6ed1b26c58
commit
55a69ae4c6
models/auth
routers/web/user/setting
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -66,6 +67,16 @@ var microcmdUserCreate = &cli.Command{
|
||||||
Name: "access-token",
|
Name: "access-token",
|
||||||
Usage: "Generate access token for the user",
|
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{
|
&cli.BoolFlag{
|
||||||
Name: "restricted",
|
Name: "restricted",
|
||||||
Usage: "Make a restricted user account",
|
Usage: "Make a restricted user account",
|
||||||
|
@ -187,23 +198,40 @@ func runCreateUser(c *cli.Context) error {
|
||||||
IsRestricted: restricted,
|
IsRestricted: restricted,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var accessTokenName string
|
||||||
|
var accessTokenScope auth_model.AccessTokenScope
|
||||||
|
if c.IsSet("access-token") {
|
||||||
|
accessTokenName = strings.TrimSpace(c.String("access-token-name"))
|
||||||
|
if accessTokenName == "" {
|
||||||
|
return errors.New("access-token-name cannot be empty")
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
} 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 {
|
if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
|
||||||
return fmt.Errorf("CreateUser: %w", err)
|
return fmt.Errorf("CreateUser: %w", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
||||||
|
|
||||||
if c.Bool("access-token") {
|
// create the access token
|
||||||
t := &auth_model.AccessToken{
|
if accessTokenScope != "" {
|
||||||
Name: "gitea-admin",
|
t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
|
||||||
UID: u.ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
fmt.Printf("Access token was successfully created... %s\n", t.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("New user '%s' has been successfully created!\n", username)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -22,6 +23,7 @@ func TestAdminUserCreate(t *testing.T) {
|
||||||
reset := func() {
|
reset := func() {
|
||||||
require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
|
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, &user_model.EmailAddress{}))
|
||||||
|
require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("MustChangePassword", func(t *testing.T) {
|
t.Run("MustChangePassword", func(t *testing.T) {
|
||||||
|
@ -48,11 +50,11 @@ func TestAdminUserCreate(t *testing.T) {
|
||||||
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
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 {
|
||||||
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)))
|
||||||
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()
|
reset()
|
||||||
assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type")
|
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")
|
assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users")
|
||||||
|
@ -63,4 +65,56 @@ func TestAdminUserCreate(t *testing.T) {
|
||||||
assert.Equal(t, user_model.UserTypeBot, u.Type)
|
assert.Equal(t, user_model.UserTypeBot, u.Type)
|
||||||
assert.Empty(t, u.Passwd)
|
assert.Empty(t, u.Passwd)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("AccessToken", func(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)
|
||||||
|
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"))
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "scopes",
|
Name: "scopes",
|
||||||
Value: "",
|
Value: "all",
|
||||||
Usage: "Comma separated list of scopes to apply to access token",
|
Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: runGenerateAccessToken,
|
Action: runGenerateAccessToken,
|
||||||
|
@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
|
||||||
|
|
||||||
func runGenerateAccessToken(c *cli.Context) error {
|
func runGenerateAccessToken(c *cli.Context) error {
|
||||||
if !c.IsSet("username") {
|
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()
|
ctx, cancel := installSignals()
|
||||||
|
@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid access token scope provided: %w", err)
|
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
|
t.Scope = accessTokenScope
|
||||||
|
|
||||||
// create the token
|
// create the token
|
||||||
|
|
|
@ -295,6 +295,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
||||||
return bitmap.toScope(), nil
|
return bitmap.toScope(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s AccessTokenScope) HasPermissionScope() bool {
|
||||||
|
return s != "" && s != AccessTokenScopePublicOnly
|
||||||
|
}
|
||||||
|
|
||||||
// PublicOnly checks if this token scope is limited to public resources
|
// PublicOnly checks if this token scope is limited to public resources
|
||||||
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
func (s AccessTokenScope) PublicOnly() (bool, error) {
|
||||||
bitmap, err := s.parse()
|
bitmap, err := s.parse()
|
||||||
|
|
|
@ -54,7 +54,7 @@ func ApplicationsPost(ctx *context.Context) {
|
||||||
ctx.ServerError("GetScope", err)
|
ctx.ServerError("GetScope", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if scope == "" || scope == auth_model.AccessTokenScopePublicOnly {
|
if !scope.HasPermissionScope() {
|
||||||
ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true)
|
ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue