From a7eecc331e817c05610d76d2e1c6f810a7b1510c Mon Sep 17 00:00:00 2001 From: "Guillaume S." Date: Tue, 25 Feb 2025 22:06:54 -0500 Subject: [PATCH 1/9] Remove Vue scoped-access-token-selector component --- models/auth/access_token_scope.go | 28 ++++++- models/auth/access_token_scope_test.go | 4 +- routers/web/user/setting/applications.go | 1 + templates/user/settings/applications.tmpl | 28 +++++-- .../components/ScopedAccessTokenSelector.vue | 81 ------------------- web_src/js/features/scoped-access-token.ts | 20 ----- web_src/js/index.ts | 2 - 7 files changed, 50 insertions(+), 114 deletions(-) delete mode 100644 web_src/js/components/ScopedAccessTokenSelector.vue delete mode 100644 web_src/js/features/scoped-access-token.ts diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 897ff3fc9ee3d..ea43bc26f93ff 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -14,7 +14,7 @@ import ( type AccessTokenScopeCategory int const ( - AccessTokenScopeCategoryActivityPub = iota + AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota AccessTokenScopeCategoryAdmin AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values AccessTokenScopeCategoryNotification @@ -38,6 +38,32 @@ var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{ AccessTokenScopeCategoryUser, } +// AccessTokenScopeCategoryNames maps AccessTokenScopeCategory to their string representations +var AccessTokenScopeCategoryNames = map[AccessTokenScopeCategory]string{ + AccessTokenScopeCategoryActivityPub: "activitypub", + AccessTokenScopeCategoryAdmin: "admin", + AccessTokenScopeCategoryMisc: "misc", + AccessTokenScopeCategoryNotification: "notification", + AccessTokenScopeCategoryOrganization: "organization", + AccessTokenScopeCategoryPackage: "package", + AccessTokenScopeCategoryIssue: "issue", + AccessTokenScopeCategoryRepository: "repository", + AccessTokenScopeCategoryUser: "user", +} + +// AccessTokenScopeCategoryNames is a list of all access token scope category names +var AllAccessTokenScopeCategoryNames = []string{ + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryActivityPub], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryAdmin], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryMisc], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryNotification], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryOrganization], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryPackage], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryIssue], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryRepository], + AccessTokenScopeCategoryNames[AccessTokenScopeCategoryUser], +} + // AccessTokenScopeLevel represents the access levels without a given scope category type AccessTokenScopeLevel int diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index a6097e45d7f59..ea9523d17f378 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -25,7 +25,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) { {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range AllAccessTokenScopeCategoryNames { tests = append(tests, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, @@ -59,7 +59,7 @@ func TestAccessTokenScope_HasScope(t *testing.T) { {"public-only", "read:issue", false, nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range AllAccessTokenScopeCategoryNames { tests = append(tests, scopeTestHasScope{ AccessTokenScope(fmt.Sprintf("read:%s", scope)), diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index cf71d01dc1624..9917f4b295a89 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -98,6 +98,7 @@ func loadApplicationsData(ctx *context.Context) { return } ctx.Data["Tokens"] = tokens + ctx.Data["TokenCategories"] = auth_model.AllAccessTokenScopeCategoryNames ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin if setting.OAuth2.Enabled { diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 31d1a2ac5b277..0adf48e85a033 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -77,14 +77,26 @@

{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}

-
-
+ {{range $category := .TokenCategories}} +
+ +
+ +
+
+ {{end}} - {{/* Fomantic ".ui.form .warning.message" is hidden by default, so put the warning message out of the form*/}} -
- {{ctx.Locale.Tr "settings.at_least_one_permission"}} -
+ {{if .EnableOAuth2}} From 3b2e0eb52f44a8b085e122f72b7af082d72a5689 Mon Sep 17 00:00:00 2001 From: "Guillaume S." Date: Wed, 26 Feb 2025 09:03:22 -0500 Subject: [PATCH 3/9] Typo --- models/auth/access_token_scope.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 998db8114dabb..81a682f8abe57 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -51,7 +51,7 @@ var AccessTokenScopeCategoryNames = map[AccessTokenScopeCategory]string{ AccessTokenScopeCategoryUser: "user", } -// AccessTokenScopeCategoryNames is a list of all access token scope category names including admin's reserved scope +// AllAccessTokenScopeCategoryNames is a list of all access token scope category names including admin's reserved scope var AllAccessTokenScopeCategoryNames = []string{ AccessTokenScopeCategoryNames[AccessTokenScopeCategoryActivityPub], AccessTokenScopeCategoryNames[AccessTokenScopeCategoryAdmin], @@ -64,7 +64,7 @@ var AllAccessTokenScopeCategoryNames = []string{ AccessTokenScopeCategoryNames[AccessTokenScopeCategoryUser], } -// AccessTokenScopeCategoryNames is a list of all access token scope category names without admin's reserved scope +// AllNonAdminAccessTokenScopeCategoryNames is a list of all access token scope category names without admin's reserved scope var AllNonAdminAccessTokenScopeCategoryNames = []string{ AccessTokenScopeCategoryNames[AccessTokenScopeCategoryActivityPub], AccessTokenScopeCategoryNames[AccessTokenScopeCategoryMisc], From 0bd9c092cb35bab691e6746267af2c627998a2dd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 11:38:20 +0800 Subject: [PATCH 4/9] simplify --- models/auth/access_token_scope.go | 48 +++++------------------ routers/web/user/setting/applications.go | 9 +++-- templates/user/settings/applications.tmpl | 36 ++++++----------- web_src/css/index.css | 1 - web_src/css/modules/select.css | 25 ------------ 5 files changed, 26 insertions(+), 93 deletions(-) delete mode 100644 web_src/css/modules/select.css diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 81a682f8abe57..7ffce053bb099 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -38,44 +38,6 @@ var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{ AccessTokenScopeCategoryUser, } -// AccessTokenScopeCategoryNames maps AccessTokenScopeCategory to their string representations -var AccessTokenScopeCategoryNames = map[AccessTokenScopeCategory]string{ - AccessTokenScopeCategoryActivityPub: "activitypub", - AccessTokenScopeCategoryAdmin: "admin", - AccessTokenScopeCategoryMisc: "misc", - AccessTokenScopeCategoryNotification: "notification", - AccessTokenScopeCategoryOrganization: "organization", - AccessTokenScopeCategoryPackage: "package", - AccessTokenScopeCategoryIssue: "issue", - AccessTokenScopeCategoryRepository: "repository", - AccessTokenScopeCategoryUser: "user", -} - -// AllAccessTokenScopeCategoryNames is a list of all access token scope category names including admin's reserved scope -var AllAccessTokenScopeCategoryNames = []string{ - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryActivityPub], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryAdmin], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryMisc], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryNotification], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryOrganization], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryPackage], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryIssue], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryRepository], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryUser], -} - -// AllNonAdminAccessTokenScopeCategoryNames is a list of all access token scope category names without admin's reserved scope -var AllNonAdminAccessTokenScopeCategoryNames = []string{ - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryActivityPub], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryMisc], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryNotification], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryOrganization], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryPackage], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryIssue], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryRepository], - AccessTokenScopeCategoryNames[AccessTokenScopeCategoryUser], -} - // AccessTokenScopeLevel represents the access levels without a given scope category type AccessTokenScopeLevel int @@ -231,6 +193,13 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A }, } +func GetAccessTokenCategories() (res []string) { + for _, cat := range accessTokenScopes[Read] { + res = append(res, strings.TrimPrefix(string(cat), "read:")) + } + return res +} + // GetRequiredScopes gets the specific scopes for a given level and categories func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope { scopes := make([]AccessTokenScope, 0, len(scopeCategories)) @@ -308,6 +277,9 @@ func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) { // StringSlice returns the AccessTokenScope as a []string func (s AccessTokenScope) StringSlice() []string { + if s == "" { + return nil + } return strings.Split(string(s), ",") } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 81fc1b8dc0874..7324698338922 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -12,6 +12,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" @@ -101,11 +102,11 @@ func loadApplicationsData(ctx *context.Context) { ctx.Data["EnableOAuth2"] = setting.OAuth2.Enabled // Handle specific ordered token categories for admin or non-admin users - if ctx.Doer.IsAdmin { - ctx.Data["TokenCategories"] = auth_model.AllAccessTokenScopeCategoryNames - } else { - ctx.Data["TokenCategories"] = auth_model.AllNonAdminAccessTokenScopeCategoryNames + tokenCategoryNames := auth_model.GetAccessTokenCategories() + if !ctx.Doer.IsAdmin { + util.SliceRemoveAll(tokenCategoryNames, "admin") } + ctx.Data["TokenCategories"] = tokenCategoryNames if setting.OAuth2.Enabled { ctx.Data["Applications"], err = db.Find[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{ diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index f5af588361b15..8e0beb3b5094c 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -70,35 +70,21 @@ {{ctx.Locale.Tr "settings.permissions_access_all"}} -
- - {{ctx.Locale.Tr "settings.select_permissions"}} - -

- {{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}} -

+
+ {{ctx.Locale.Tr "settings.select_permissions"}} + {{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}} {{range $category := .TokenCategories}} -
- -
- -
+
+ +
{{end}}
- diff --git a/web_src/css/index.css b/web_src/css/index.css index ce1a23b245cbc..630aa3c2ef03d 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -19,7 +19,6 @@ @import "./modules/dimmer.css"; @import "./modules/modal.css"; -@import "./modules/select.css"; @import "./modules/tippy.css"; @import "./modules/breadcrumb.css"; @import "./modules/comment.css"; diff --git a/web_src/css/modules/select.css b/web_src/css/modules/select.css deleted file mode 100644 index 1d7d749d4ad05..0000000000000 --- a/web_src/css/modules/select.css +++ /dev/null @@ -1,25 +0,0 @@ -.gitea-select { - position: relative; -} - -.gitea-select select { - appearance: none; /* hide default triangle */ -} - -/* ::before and ::after pseudo elements don't work on select elements, - so we need to put it on the parent. */ -.gitea-select::after { - position: absolute; - top: 12px; - right: 8px; - pointer-events: none; - content: ""; - width: 14px; - height: 14px; - mask-size: cover; - -webkit-mask-size: cover; - mask-image: var(--octicon-chevron-right); - -webkit-mask-image: var(--octicon-chevron-right); - transform: rotate(90deg); /* point the chevron down */ - background: currentcolor; -} From f4fd29411ad3ba772ee45c6b119ec6211577f2f9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 12:30:34 +0800 Subject: [PATCH 5/9] optimize ui --- models/auth/access_token_scope.go | 2 + options/locale/locale_en-US.ini | 1 - routers/web/user/setting/applications.go | 24 ++++-- services/context/context.go | 10 +-- services/forms/user_form.go | 11 +-- templates/user/settings/applications.tmpl | 73 +++++++++---------- .../settings/applications_oauth2_list.tmpl | 54 +++++++------- web_src/css/modules/checkbox.css | 10 +++ 8 files changed, 98 insertions(+), 87 deletions(-) diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 7ffce053bb099..0e5b2e96e6602 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -5,6 +5,7 @@ package auth import ( "fmt" + "slices" "strings" "code.gitea.io/gitea/models/perm" @@ -197,6 +198,7 @@ func GetAccessTokenCategories() (res []string) { for _, cat := range accessTokenScopes[Read] { res = append(res, strings.TrimPrefix(string(cat), "read:")) } + slices.Sort(res) return res } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 4c25ba320555d..5aa6d19daee40 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -917,7 +917,6 @@ delete_token_success = The token has been deleted. Applications using it no long repo_and_org_access = Repository and Organization Access permissions_public_only = Public only permissions_access_all = All (public, private, and limited) -select_permissions = Select permissions permission_not_set = Not set permission_no_access = No Access permission_read = Read diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 7324698338922..f1e7a1b8d1d30 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -6,6 +6,7 @@ package setting import ( "net/http" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" @@ -40,18 +41,29 @@ func ApplicationsPost(ctx *context.Context) { ctx.Data["PageIsSettingsApplications"] = true ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer) - if ctx.HasError() { - loadApplicationsData(ctx) - - ctx.HTML(http.StatusOK, tplSettingsApplications) - return + _ = ctx.Req.ParseForm() + var scopeNames []string + for k, v := range ctx.Req.Form { + if strings.HasPrefix(k, "scope-") { + scopeNames = append(scopeNames, v...) + } } - scope, err := form.GetScope() + scope, err := auth_model.AccessTokenScope(strings.Join(scopeNames, ",")).Normalize() if err != nil { ctx.ServerError("GetScope", err) return } + if scope == "" { + ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) + } + + if ctx.HasError() { + loadApplicationsData(ctx) + ctx.HTML(http.StatusOK, tplSettingsApplications) + return + } + t := &auth_model.AccessToken{ UID: ctx.Doer.ID, Name: form.Name, diff --git a/services/context/context.go b/services/context/context.go index 5e08fba44261e..fce90218792f4 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -213,13 +213,13 @@ func Contexter() func(next http.Handler) http.Handler { // Attention: this function changes ctx.Data and ctx.Flash // If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. func (ctx *Context) HasError() bool { - hasErr, ok := ctx.Data["HasError"] - if !ok { - return false + hasErr, _ := ctx.Data["HasError"].(bool) + hasErr = hasErr || ctx.Flash.ErrorMsg != "" + if ctx.Flash.ErrorMsg == "" { + ctx.Flash.ErrorMsg = ctx.GetErrMsg() } - ctx.Flash.ErrorMsg = ctx.GetErrMsg() ctx.Data["Flash"] = ctx.Flash - return hasErr.(bool) + return hasErr } // GetErrMsg returns error message in form validation. diff --git a/services/forms/user_form.go b/services/forms/user_form.go index ed79936add68e..c9ce71e8861c2 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -7,9 +7,7 @@ package forms import ( "mime/multipart" "net/http" - "strings" - auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" @@ -347,8 +345,7 @@ func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) bind // NewAccessTokenForm form for creating access token type NewAccessTokenForm struct { - Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` - Scope []string + Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"` } // Validate validates the fields @@ -357,12 +354,6 @@ func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) bi return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) { - scope := strings.Join(f.Scope, ",") - s, err := auth_model.AccessTokenScope(scope).Normalize() - return s, err -} - // EditOAuth2ApplicationForm form for editing oauth2 applications type EditOAuth2ApplicationForm struct { Name string `binding:"Required;MaxSize(255)" form:"application_name"` diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 8e0beb3b5094c..547dc90c661bc 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -50,44 +50,41 @@
-
- {{ctx.Locale.Tr "settings.generate_new_token"}} -
-
- {{.CsrfTokenHtml}} -
- - -
-
- - - -
-
- {{ctx.Locale.Tr "settings.select_permissions"}} - {{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}} - {{range $category := .TokenCategories}} -
- - -
- {{end}} -
- -
+
+

{{ctx.Locale.Tr "settings.generate_new_token"}}

+
+ {{.CsrfTokenHtml}} +
+ + +
+
+
{{ctx.Locale.Tr "settings.repo_and_org_access"}}
+ + +
+
+
{{ctx.Locale.Tr "settings.access_token_desc" (HTMLFormat `href="%s/api/swagger" target="_blank"` AppSubUrl) (`href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`|SafeHTML)}}
+ + {{range $category := .TokenCategories}} + + + + + + + {{end}} +
{{$category}}
+
+ +
+
{{if .EnableOAuth2}} diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl index 61098e118b430..418d8e9cfc1ac 100644 --- a/templates/user/settings/applications_oauth2_list.tmpl +++ b/templates/user/settings/applications_oauth2_list.tmpl @@ -48,33 +48,33 @@
-
- {{ctx.Locale.Tr "settings.create_oauth2_application"}} -
-
- {{.CsrfTokenHtml}} -
- - -
-
- - -
-
-
- - +
+

{{ctx.Locale.Tr "settings.create_oauth2_application"}}

+ + {{.CsrfTokenHtml}} +
+ +
-
-
-
- - +
+ +
-
- - +
+
+ + +
+
+
+
+ + +
+
+ + +
diff --git a/web_src/css/modules/checkbox.css b/web_src/css/modules/checkbox.css index 0a3a71acaa693..f7e61ba360ff1 100644 --- a/web_src/css/modules/checkbox.css +++ b/web_src/css/modules/checkbox.css @@ -119,3 +119,13 @@ input[type="radio"] { .ui.toggle.checkbox input:focus:checked ~ label::before { background: var(--color-primary) !important; } + +label.gt-checkbox { + display: inline-flex; + align-items: center; + gap: 0.25em; +} + +.ui.form .field > label.gt-checkbox { + display: flex; +} From a77e5645e75e542d63dfcd7dc10eedfe615ee0af Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 12:36:37 +0800 Subject: [PATCH 6/9] fix tests --- models/auth/access_token_scope_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index ea9523d17f378..ddbc667e8dd43 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -17,6 +17,7 @@ type scopeTestNormalize struct { } func TestAccessTokenScope_Normalize(t *testing.T) { + assert.Equal(t, []string([]string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}), GetAccessTokenCategories()) tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, @@ -25,7 +26,7 @@ func TestAccessTokenScope_Normalize(t *testing.T) { {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, } - for _, scope := range AllAccessTokenScopeCategoryNames { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, @@ -59,7 +60,7 @@ func TestAccessTokenScope_HasScope(t *testing.T) { {"public-only", "read:issue", false, nil}, } - for _, scope := range AllAccessTokenScopeCategoryNames { + for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestHasScope{ AccessTokenScope(fmt.Sprintf("read:%s", scope)), From 35aa33de9384bb8c02703ae4d8d177e6fdb97fe8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 13:14:15 +0800 Subject: [PATCH 7/9] fix tests --- models/auth/access_token_scope_test.go | 2 +- services/context/context.go | 3 ++ services/forms/user_form_test.go | 27 ---------- tests/integration/api_admin_org_test.go | 2 +- tests/integration/api_admin_test.go | 4 +- .../api_helper_for_declarative_test.go | 4 ++ tests/integration/api_repo_git_blobs_test.go | 2 +- tests/integration/api_repo_git_trees_test.go | 2 +- tests/integration/integration_test.go | 50 +++---------------- 9 files changed, 20 insertions(+), 76 deletions(-) diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index ddbc667e8dd43..9e4aa83633736 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -17,7 +17,7 @@ type scopeTestNormalize struct { } func TestAccessTokenScope_Normalize(t *testing.T) { - assert.Equal(t, []string([]string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}), GetAccessTokenCategories()) + assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories()) tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, diff --git a/services/context/context.go b/services/context/context.go index fce90218792f4..f3a0f0bb5f57a 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -215,6 +215,9 @@ func Contexter() func(next http.Handler) http.Handler { func (ctx *Context) HasError() bool { hasErr, _ := ctx.Data["HasError"].(bool) hasErr = hasErr || ctx.Flash.ErrorMsg != "" + if !hasErr { + return false + } if ctx.Flash.ErrorMsg == "" { ctx.Flash.ErrorMsg = ctx.GetErrMsg() } diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go index 66050187c9f1c..b4120f20ed873 100644 --- a/services/forms/user_form_test.go +++ b/services/forms/user_form_test.go @@ -4,10 +4,8 @@ package forms import ( - "strconv" "testing" - auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/setting" "github.com/gobwas/glob" @@ -104,28 +102,3 @@ func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) { assert.Equal(t, v.valid, form.IsEmailDomainAllowed()) } } - -func TestNewAccessTokenForm_GetScope(t *testing.T) { - tests := []struct { - form NewAccessTokenForm - scope auth_model.AccessTokenScope - expectedErr error - }{ - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}}, - scope: "read:repository", - }, - { - form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}}, - scope: "read:repository,write:user", - }, - } - - for i, test := range tests { - t.Run(strconv.Itoa(i), func(t *testing.T) { - scope, err := test.form.GetScope() - assert.Equal(t, test.expectedErr, err) - assert.Equal(t, test.scope, scope) - }) - } -} diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index a29d0ba1d7467..b243856127dd5 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -76,7 +76,7 @@ func TestAPIAdminOrgCreateNotAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" session := loginUser(t, nonAdminUsername) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) org := api.CreateOrgOption{ UserName: "user2_org", FullName: "User2's organization", diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 66209ee4e0c89..b42f05fc55028 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -76,7 +76,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { var newPublicKey api.PublicKey DecodeJSON(t, resp, &newPublicKey) - token = getUserToken(t, normalUsername) + token = getUserToken(t, normalUsername, auth_model.AccessTokenScopeAll) req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) @@ -139,7 +139,7 @@ func TestAPIListUsersNotLoggedIn(t *testing.T) { func TestAPIListUsersNonAdmin(t *testing.T) { defer tests.PrepareTestEnv(t)() nonAdminUsername := "user2" - token := getUserToken(t, nonAdminUsername) + token := getUserToken(t, nonAdminUsername, auth_model.AccessTokenScopeAll) req := NewRequest(t, "GET", "/api/v1/admin/users"). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 96669b46f02d1..f3a595540f2f0 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -33,6 +33,10 @@ type APITestContext struct { func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext { session := loginUser(t, username) + if len(scope) == 0 { + // FIXME: legacy logic: no scope means all + scope = []auth.AccessTokenScope{auth.AccessTokenScopeAll} + } token := getTokenForLoggedInUser(t, session, scope...) return APITestContext{ Session: session, diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 184362e7e320f..9c4be31396798 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -72,7 +72,7 @@ func TestAPIReposGitBlobs(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index 8eec6d8d220d6..47063d9091207 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -69,7 +69,7 @@ func TestAPIReposGitTrees(t *testing.T) { // Login as User4. session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAll) // Test using org repo "org3/repo3" where user4 is a NOT collaborator req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/trees/d56a3073c1dbb7b15963110a049d50cdb5db99fc?access=%s", org3.Name, repo3.Name, token4) diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 9e487924d1fca..2f6b7eae31617 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -249,55 +249,19 @@ func loginUserWithPassword(t testing.TB, userName, password string) *TestSession // token has to be unique this counter take care of var tokenCounter int64 -// getTokenForLoggedInUser returns a token for a logged in user. -// The scope is an optional list of snake_case strings like the frontend form fields, -// but without the "scope_" prefix. +// getTokenForLoggedInUser returns a token for a logged-in user. func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string { t.Helper() - var token string - req := NewRequest(t, "GET", "/user/settings/applications") - resp := session.MakeRequest(t, req, http.StatusOK) - var csrf string - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != "_csrf" { - continue - } - csrf = cookie.Value - break - } - if csrf == "" { - doc := NewHTMLParser(t, resp.Body) - csrf = doc.GetCSRF() - } - assert.NotEmpty(t, csrf) urlValues := url.Values{} - urlValues.Add("_csrf", csrf) + urlValues.Add("_csrf", GetUserCSRFToken(t, session)) urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1))) for _, scope := range scopes { - urlValues.Add("scope", string(scope)) + urlValues.Add("scope-dummy", string(scope)) // it only needs to start with "scope-" to be accepted } - req = NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) - resp = session.MakeRequest(t, req, http.StatusSeeOther) - - // Log the flash values on failure - if !assert.Equal(t, []string{"/user/settings/applications"}, resp.Result().Header["Location"]) { - for _, cookie := range resp.Result().Cookies() { - if cookie.Name != gitea_context.CookieNameFlash { - continue - } - flash, _ := url.ParseQuery(cookie.Value) - for key, value := range flash { - t.Logf("Flash %q: %q", key, value) - } - } - } - - req = NewRequest(t, "GET", "/user/settings/applications") - resp = session.MakeRequest(t, req, http.StatusOK) - htmlDoc := NewHTMLParser(t, resp.Body) - token = htmlDoc.doc.Find(".ui.info p").Text() - assert.NotEmpty(t, token) - return token + req := NewRequestWithURLValues(t, "POST", "/user/settings/applications", urlValues) + session.MakeRequest(t, req, http.StatusSeeOther) + flashes := session.GetCookieFlashMessage() + return flashes.InfoMsg } type RequestWrapper struct { From 8f446cbbd73640fefdbcc9a019e43ac0f7d91f70 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 16:13:28 +0800 Subject: [PATCH 8/9] fix public-only input --- routers/web/user/setting/applications.go | 2 +- templates/user/settings/applications.tmpl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index f1e7a1b8d1d30..e017663159555 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -54,7 +54,7 @@ func ApplicationsPost(ctx *context.Context) { ctx.ServerError("GetScope", err) return } - if scope == "" { + if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) } diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 547dc90c661bc..501f238c7a377 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -61,10 +61,10 @@
{{ctx.Locale.Tr "settings.repo_and_org_access"}}
From bce0e62a50017218caf9427a69fb36495f3559ce Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 27 Feb 2025 19:16:42 +0800 Subject: [PATCH 9/9] fix token names --- routers/web/user/setting/applications.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index e017663159555..1f6c97a5cc681 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -116,7 +116,7 @@ func loadApplicationsData(ctx *context.Context) { // Handle specific ordered token categories for admin or non-admin users tokenCategoryNames := auth_model.GetAccessTokenCategories() if !ctx.Doer.IsAdmin { - util.SliceRemoveAll(tokenCategoryNames, "admin") + tokenCategoryNames = util.SliceRemoveAll(tokenCategoryNames, "admin") } ctx.Data["TokenCategories"] = tokenCategoryNames