aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/admin_user_create.go45
-rw-r--r--cmd/admin_user_create_test.go98
-rw-r--r--cmd/admin_user_generate_access_token.go9
-rw-r--r--cmd/migrate.go2
-rw-r--r--cmd/web_acme.go23
5 files changed, 140 insertions, 37 deletions
diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index bf8cbc7c4c..37853cd312 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"
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
Name: "access-token",
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",
Usage: "Make a restricted user account",
@@ -162,23 +173,39 @@ 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") {
+ 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")
}
- if c.Bool("access-token") {
- t := &auth_model.AccessToken{
- Name: "gitea-admin",
- UID: u.ID,
- }
+ // 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)
}
-
fmt.Printf("New user '%s' has been successfully created!\n", username)
return nil
}
diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go
index 83754e97b1..9f109b888e 100644
--- a/cmd/admin_user_create_test.go
+++ b/cmd/admin_user_create_test.go
@@ -8,37 +8,97 @@ 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"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestAdminUserCreate(t *testing.T) {
app := NewMainApp(AppVersion{})
reset := func() {
- assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
- assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
+ 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) {
+ type check struct{ IsAdmin, MustChangePassword bool }
+ createCheck := func(name, args string) check {
+ assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
+ u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
+ return check{u.IsAdmin, u.MustChangePassword}
+ }
+ reset()
+ assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
- type createCheck struct{ IsAdmin, MustChangePassword bool }
- createUser := func(name, args string) createCheck {
- assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
- u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
- return createCheck{u.IsAdmin, u.MustChangePassword}
+ reset()
+ assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
+
+ reset()
+ assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
+ assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
+ assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
+ assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
+ assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
+ })
+
+ 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)))
}
- reset()
- assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
-
- reset()
- assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
-
- reset()
- assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
- assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
- assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
- assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
- assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
+
+ 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")
+ })
}
diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go
index 6c2c10494e..f6db7a74bd 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/cmd/migrate.go b/cmd/migrate.go
index 4e4dd45af3..459805a76d 100644
--- a/cmd/migrate.go
+++ b/cmd/migrate.go
@@ -18,7 +18,7 @@ import (
var CmdMigrate = &cli.Command{
Name: "migrate",
Usage: "Migrate the database",
- Description: "This is a command for migrating the database, so that you can run gitea admin create-user before starting the server.",
+ Description: `This is a command for migrating the database, so that you can run "gitea admin create user" before starting the server.`,
Action: runMigrate,
}
diff --git a/cmd/web_acme.go b/cmd/web_acme.go
index 90e4a02764..172dde913b 100644
--- a/cmd/web_acme.go
+++ b/cmd/web_acme.go
@@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"github.com/caddyserver/certmagic"
)
@@ -54,8 +55,6 @@ func runACME(listenAddr string, m http.Handler) error {
altTLSALPNPort = p
}
- magic := certmagic.NewDefault()
- magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
// Try to use private CA root if provided, otherwise defaults to system's trust
var certPool *x509.CertPool
if setting.AcmeCARoot != "" {
@@ -65,8 +64,20 @@ func runACME(listenAddr string, m http.Handler) error {
log.Warn("Failed to parse CA Root certificate, using default CA trust: %v", err)
}
}
- myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
- CA: setting.AcmeURL,
+ // FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
+ // Ideally it should migrate to AppDataPath write to "AppDataPath/https"
+ // And one more thing, no idea why we should set the global default variables here
+ // But it seems that the current ACME code needs these global variables to make renew work.
+ // Otherwise, "renew" will use incorrect storage path
+ oldDefaultACME := certmagic.DefaultACME
+ certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
+ certmagic.DefaultACME = certmagic.ACMEIssuer{
+ // try to use the default values provided by DefaultACME
+ CA: util.IfZero(setting.AcmeURL, oldDefaultACME.CA),
+ TestCA: oldDefaultACME.TestCA,
+ Logger: oldDefaultACME.Logger,
+ HTTPProxy: oldDefaultACME.HTTPProxy,
+
TrustedRoots: certPool,
Email: setting.AcmeEmail,
Agreed: setting.AcmeTOS,
@@ -75,8 +86,10 @@ func runACME(listenAddr string, m http.Handler) error {
ListenHost: setting.HTTPAddr,
AltTLSALPNPort: altTLSALPNPort,
AltHTTPPort: altHTTPPort,
- })
+ }
+ magic := certmagic.NewDefault()
+ myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
magic.Issuers = []certmagic.Issuer{myACME}
// this obtains certificates or renews them if necessary