]> source.dussan.org Git - gitea.git/commitdiff
Add scopes to API to create token and display them (#22989)
authorzeripath <art27@cantab.net>
Mon, 20 Feb 2023 21:28:44 +0000 (21:28 +0000)
committerGitHub <noreply@github.com>
Mon, 20 Feb 2023 21:28:44 +0000 (15:28 -0600)
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>
models/auth/token_scope.go
modules/structs/user_app.go
options/locale/locale_en-US.ini
routers/api/v1/user/app.go
templates/swagger/v1_json.tmpl
templates/user/settings/applications.tmpl

index c61c306496b49fbc0a52ba03f8dd3c4ab9200762..38733a1c8f30549a00c68046be1b4f4beb8174c0 100644 (file)
@@ -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()
index 3a5ae34df19a872bae59e87eccc0f91170bb3c10..7f78fbd495e2424e639de2bdc8c0f3ba555d1372 100644 (file)
@@ -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
index 92ca5be8d3584a696111a45d8e20fb59c82e4749..df66ce23390ff2240e688e921f93fa5dc8481b87 100644 (file)
@@ -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
index 7b2f0d8c30d2ee728220c64db0bbd597d46a65fd..f89d53945fa0b3189037651b7acc639162e50f91 100644 (file)
@@ -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
index 2a675766abc9254d38d08a8f22d4f2c6acad2583..de774deaed13ac6376159458638ad112f0de73d5 100644 (file)
         "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"
           "type": "string",
           "x-go-name": "Name"
         },
+        "scopes": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "Scopes"
+        },
         "sha1": {
           "type": "string",
           "x-go-name": "Token"
     "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"
index 439ed5e1483fd5935325a56dd9c1078f4be7458e..ef9ac9a97784c7cc6f02ffcb9636f1db531af4c7 100644 (file)
                                                </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>