diff --git a/models/auth/token_scope.go b/models/auth/token_scope.go index c61c30649..38733a1c8 100644 --- a/models/auth/token_scope.go +++ b/models/auth/token_scope.go @@ -168,10 +168,23 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{ // Parse parses the scope string into a bitmap, thus removing possible duplicates. func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) { - list := strings.Split(string(s), ",") - var bitmap AccessTokenScopeBitmap - for _, v := range list { + + // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code + remainingScopes := string(s) + for len(remainingScopes) > 0 { + i := strings.IndexByte(remainingScopes, ',') + var v string + if i < 0 { + v = remainingScopes + remainingScopes = "" + } else if i+1 >= len(remainingScopes) { + v = remainingScopes[:i] + remainingScopes = "" + } else { + v = remainingScopes[:i] + remainingScopes = remainingScopes[i+1:] + } singleScope := AccessTokenScope(v) if singleScope == "" { continue @@ -187,9 +200,15 @@ func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) { } bitmap |= bits } + return bitmap, nil } +// StringSlice returns the AccessTokenScope as a []string +func (s AccessTokenScope) StringSlice() []string { + return strings.Split(string(s), ",") +} + // Normalize returns a normalized scope string without any duplicates. func (s AccessTokenScope) Normalize() (AccessTokenScope, error) { bitmap, err := s.Parse() diff --git a/modules/structs/user_app.go b/modules/structs/user_app.go index 3a5ae34df..7f78fbd49 100644 --- a/modules/structs/user_app.go +++ b/modules/structs/user_app.go @@ -11,10 +11,11 @@ import ( // AccessToken represents an API access token. // swagger:response AccessToken type AccessToken struct { - ID int64 `json:"id"` - Name string `json:"name"` - Token string `json:"sha1"` - TokenLastEight string `json:"token_last_eight"` + ID int64 `json:"id"` + Name string `json:"name"` + Token string `json:"sha1"` + TokenLastEight string `json:"token_last_eight"` + Scopes []string `json:"scopes"` } // AccessTokenList represents a list of API access token. @@ -22,9 +23,10 @@ type AccessToken struct { type AccessTokenList []*AccessToken // CreateAccessTokenOption options when create access token -// swagger:parameters userCreateToken type CreateAccessTokenOption struct { - Name string `json:"name" binding:"Required"` + // required: true + Name string `json:"name" binding:"Required"` + Scopes []string `json:"scopes"` } // CreateOAuth2ApplicationOptions holds options to create an oauth2 application diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 92ca5be8d..df66ce233 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -757,6 +757,7 @@ access_token_deletion_confirm_action = Delete access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue? delete_token_success = The token has been deleted. Applications using it no longer have access to your account. select_scopes = Select scopes +scopes_list = Scopes: manage_oauth2_applications = Manage OAuth2 Applications edit_oauth2_application = Edit OAuth2 Application diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index 7b2f0d8c3..f89d53945 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "strconv" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/context" @@ -62,6 +63,7 @@ func ListAccessTokens(ctx *context.APIContext) { ID: tokens[i].ID, Name: tokens[i].Name, TokenLastEight: tokens[i].TokenLastEight, + Scopes: tokens[i].Scope.StringSlice(), } } @@ -82,9 +84,9 @@ func CreateAccessToken(ctx *context.APIContext) { // - name: username // in: path // description: username of user - // type: string // required: true - // - name: userCreateToken + // type: string + // - name: body // in: body // schema: // "$ref": "#/definitions/CreateAccessTokenOption" @@ -111,6 +113,13 @@ func CreateAccessToken(ctx *context.APIContext) { return } + scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize() + if err != nil { + ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err)) + return + } + t.Scope = scope + if err := auth_model.NewAccessToken(t); err != nil { ctx.Error(http.StatusInternalServerError, "NewAccessToken", err) return diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 2a675766a..de774deae 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -14084,14 +14084,13 @@ "parameters": [ { "type": "string", - "x-go-name": "Name", "description": "username of user", "name": "username", "in": "path", "required": true }, { - "name": "userCreateToken", + "name": "body", "in": "body", "schema": { "$ref": "#/definitions/CreateAccessTokenOption" @@ -14194,6 +14193,13 @@ "type": "string", "x-go-name": "Name" }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Scopes" + }, "sha1": { "type": "string", "x-go-name": "Token" @@ -14925,10 +14931,20 @@ "CreateAccessTokenOption": { "description": "CreateAccessTokenOption options when create access token", "type": "object", + "required": [ + "name" + ], "properties": { "name": { "type": "string", "x-go-name": "Name" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "Scopes" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 439ed5e14..ef9ac9a97 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -21,7 +21,14 @@ {{svg "fontawesome-send" 36}}
- {{.Name}} +
{{.Name}} +

{{$.locale.Tr "settings.scopes_list"}}

+ +
{{$.locale.Tr "settings.add_on"}} — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} {{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}