Skip to content

Commit

Permalink
Add scopes to API to create token and display them (#22989)
Browse files Browse the repository at this point in the history
The API to create tokens is missing the ability to set the required
scopes for tokens, and to show them on the API and on the UI.

This PR adds this functionality.

Signed-off-by: Andrew Thornton <art27@cantab.net>
  • Loading branch information
zeripath committed Feb 20, 2023
1 parent 330b166 commit d2128b4
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 14 deletions.
25 changes: 22 additions & 3 deletions models/auth/token_scope.go
Expand Up @@ -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
Expand All @@ -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()
Expand Down
14 changes: 8 additions & 6 deletions modules/structs/user_app.go
Expand Up @@ -11,20 +11,22 @@ 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.
// swagger:response AccessTokenList
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
Expand Down
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions routers/api/v1/user/app.go
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"net/http"
"strconv"
"strings"

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/context"
Expand Down Expand Up @@ -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(),
}
}

Expand All @@ -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"
Expand All @@ -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
Expand Down
20 changes: 18 additions & 2 deletions templates/swagger/v1_json.tmpl
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
9 changes: 8 additions & 1 deletion templates/user/settings/applications.tmpl
Expand Up @@ -21,7 +21,14 @@
</div>
<i class="icon tooltip{{if .HasRecentActivity}} green{{end}}" {{if .HasRecentActivity}}data-content="{{$.locale.Tr "settings.token_state_desc"}}"{{end}}>{{svg "fontawesome-send" 36}}</i>
<div class="content">
<strong>{{.Name}}</strong>
<details><summary><strong>{{.Name}}</strong></summary>
<p class="gt-my-2">{{$.locale.Tr "settings.scopes_list"}}</p>
<ul class="gt-my-2">
{{range .Scope.StringSlice}}
<li>{{.}}</li>
{{end}}
</ul>
</details>
<div class="activity meta">
<i>{{$.locale.Tr "settings.add_on"}} <span><time data-format="short-date" datetime="{{.CreatedUnix.FormatLong}}">{{.CreatedUnix.FormatShort}}</time></span> — {{svg "octicon-info"}} {{if .HasUsed}}{{$.locale.Tr "settings.last_used"}} <span {{if .HasRecentActivity}}class="green"{{end}}><time data-format="short-date" datetime="{{.UpdatedUnix.FormatLong}}">{{.UpdatedUnix.FormatShort}}</time></span>{{else}}{{$.locale.Tr "settings.no_activity"}}{{end}}</i>
</div>
Expand Down

0 comments on commit d2128b4

Please sign in to comment.