mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-27 12:53:57 +01:00
Add scopes to API to create token and display them (#22989)
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>
This commit is contained in:
parent
330b166423
commit
d2128b44f7
6 changed files with 68 additions and 14 deletions
|
@ -168,10 +168,23 @@ var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{
|
||||||
|
|
||||||
// Parse parses the scope string into a bitmap, thus removing possible duplicates.
|
// Parse parses the scope string into a bitmap, thus removing possible duplicates.
|
||||||
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
|
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
|
||||||
list := strings.Split(string(s), ",")
|
|
||||||
|
|
||||||
var bitmap AccessTokenScopeBitmap
|
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)
|
singleScope := AccessTokenScope(v)
|
||||||
if singleScope == "" {
|
if singleScope == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -187,9 +200,15 @@ func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
|
||||||
}
|
}
|
||||||
bitmap |= bits
|
bitmap |= bits
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitmap, nil
|
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.
|
// Normalize returns a normalized scope string without any duplicates.
|
||||||
func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
|
||||||
bitmap, err := s.Parse()
|
bitmap, err := s.Parse()
|
||||||
|
|
|
@ -11,10 +11,11 @@ import (
|
||||||
// AccessToken represents an API access token.
|
// AccessToken represents an API access token.
|
||||||
// swagger:response AccessToken
|
// swagger:response AccessToken
|
||||||
type AccessToken struct {
|
type AccessToken struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Token string `json:"sha1"`
|
Token string `json:"sha1"`
|
||||||
TokenLastEight string `json:"token_last_eight"`
|
TokenLastEight string `json:"token_last_eight"`
|
||||||
|
Scopes []string `json:"scopes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessTokenList represents a list of API access token.
|
// AccessTokenList represents a list of API access token.
|
||||||
|
@ -22,9 +23,10 @@ type AccessToken struct {
|
||||||
type AccessTokenList []*AccessToken
|
type AccessTokenList []*AccessToken
|
||||||
|
|
||||||
// CreateAccessTokenOption options when create access token
|
// CreateAccessTokenOption options when create access token
|
||||||
// swagger:parameters userCreateToken
|
|
||||||
type CreateAccessTokenOption struct {
|
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
|
// CreateOAuth2ApplicationOptions holds options to create an oauth2 application
|
||||||
|
|
|
@ -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?
|
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.
|
delete_token_success = The token has been deleted. Applications using it no longer have access to your account.
|
||||||
select_scopes = Select scopes
|
select_scopes = Select scopes
|
||||||
|
scopes_list = Scopes:
|
||||||
|
|
||||||
manage_oauth2_applications = Manage OAuth2 Applications
|
manage_oauth2_applications = Manage OAuth2 Applications
|
||||||
edit_oauth2_application = Edit OAuth2 Application
|
edit_oauth2_application = Edit OAuth2 Application
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
@ -62,6 +63,7 @@ func ListAccessTokens(ctx *context.APIContext) {
|
||||||
ID: tokens[i].ID,
|
ID: tokens[i].ID,
|
||||||
Name: tokens[i].Name,
|
Name: tokens[i].Name,
|
||||||
TokenLastEight: tokens[i].TokenLastEight,
|
TokenLastEight: tokens[i].TokenLastEight,
|
||||||
|
Scopes: tokens[i].Scope.StringSlice(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,9 +84,9 @@ func CreateAccessToken(ctx *context.APIContext) {
|
||||||
// - name: username
|
// - name: username
|
||||||
// in: path
|
// in: path
|
||||||
// description: username of user
|
// description: username of user
|
||||||
// type: string
|
|
||||||
// required: true
|
// required: true
|
||||||
// - name: userCreateToken
|
// type: string
|
||||||
|
// - name: body
|
||||||
// in: body
|
// in: body
|
||||||
// schema:
|
// schema:
|
||||||
// "$ref": "#/definitions/CreateAccessTokenOption"
|
// "$ref": "#/definitions/CreateAccessTokenOption"
|
||||||
|
@ -111,6 +113,13 @@ func CreateAccessToken(ctx *context.APIContext) {
|
||||||
return
|
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 {
|
if err := auth_model.NewAccessToken(t); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
|
ctx.Error(http.StatusInternalServerError, "NewAccessToken", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -14084,14 +14084,13 @@
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name",
|
|
||||||
"description": "username of user",
|
"description": "username of user",
|
||||||
"name": "username",
|
"name": "username",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "userCreateToken",
|
"name": "body",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/CreateAccessTokenOption"
|
"$ref": "#/definitions/CreateAccessTokenOption"
|
||||||
|
@ -14194,6 +14193,13 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name"
|
"x-go-name": "Name"
|
||||||
},
|
},
|
||||||
|
"scopes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Scopes"
|
||||||
|
},
|
||||||
"sha1": {
|
"sha1": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Token"
|
"x-go-name": "Token"
|
||||||
|
@ -14925,10 +14931,20 @@
|
||||||
"CreateAccessTokenOption": {
|
"CreateAccessTokenOption": {
|
||||||
"description": "CreateAccessTokenOption options when create access token",
|
"description": "CreateAccessTokenOption options when create access token",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name"
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"scopes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "Scopes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
|
@ -21,7 +21,14 @@
|
||||||
</div>
|
</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>
|
<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">
|
<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">
|
<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>
|
<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>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue