From a860b3e101293e8f398d34813a97ecc30f9dfdf8 Mon Sep 17 00:00:00 2001 From: Aiden Scandella Date: Fri, 29 Nov 2024 11:18:21 -0800 Subject: [PATCH 01/17] WIP --- models/auth/access_token_scope.go | 17 +++++++++++++++-- models/auth/access_token_scope_test.go | 6 +++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 897ff3fc9e..c0d6ef140f 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -23,6 +23,7 @@ const ( AccessTokenScopeCategoryIssue AccessTokenScopeCategoryRepository AccessTokenScopeCategoryUser + AccessTokenScopeCategoryCommitStatus ) // AllAccessTokenScopeCategories contains all access token scope categories @@ -36,6 +37,7 @@ var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{ AccessTokenScopeCategoryIssue, AccessTokenScopeCategoryRepository, AccessTokenScopeCategoryUser, + AccessTokenScopeCategoryCommitStatus, } // AccessTokenScopeLevel represents the access levels without a given scope category @@ -81,6 +83,9 @@ const ( AccessTokenScopeReadUser AccessTokenScope = "read:user" AccessTokenScopeWriteUser AccessTokenScope = "write:user" + + AccessTokenScopeReadCommitStatus AccessTokenScope = "read:commitstatus" + AccessTokenScopeWriteCommitStatus AccessTokenScope = "write:commitstatus" ) // accessTokenScopeBitmap represents a bitmap of access token scopes. @@ -92,7 +97,7 @@ const ( accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits | accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits | accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits | - accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits + accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits | accessTokenScopeWriteCommitStatusBits accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota @@ -123,6 +128,9 @@ const ( accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1< 64 scopes, // refactoring the whole implementation in this file (and only this file) is needed. @@ -141,6 +149,7 @@ var allAccessTokenScopes = []AccessTokenScope{ AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue, AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository, AccessTokenScopeWriteUser, AccessTokenScopeReadUser, + AccessTokenScopeWriteCommitStatus, AccessTokenScopeReadCommitStatus, } // allAccessTokenScopeBits contains all access token scopes. @@ -165,6 +174,8 @@ var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{ AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits, AccessTokenScopeReadUser: accessTokenScopeReadUserBits, AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits, + AccessTokenScopeReadCommitStatus: accessTokenScopeReadCommitStatusBits, + AccessTokenScopeWriteCommitStatus: accessTokenScopeWriteCommitStatusBits, } // readAccessTokenScopes maps a scope category to the read permission scope @@ -179,6 +190,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue, AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository, AccessTokenScopeCategoryUser: AccessTokenScopeReadUser, + AccessTokenScopeCategoryCommitStatus: AccessTokenScopeReadCommitStatus, }, Write: { AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub, @@ -190,6 +202,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue, AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository, AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser, + AccessTokenScopeCategoryCommitStatus: AccessTokenScopeWriteCommitStatus, }, } @@ -359,7 +372,7 @@ func (bitmap accessTokenScopeBitmap) toScope() AccessTokenScope { scope := AccessTokenScope(strings.Join(scopes, ",")) scope = AccessTokenScope(strings.ReplaceAll( string(scope), - "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", + "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,write:commitstatus", "all", )) return scope diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index a6097e45d7..531e29b6f2 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -21,11 +21,11 @@ func TestAccessTokenScope_Normalize(t *testing.T) { {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, {"all", "all", nil}, - {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", nil}, - {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,public-only", "public-only,all", nil}, + {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,write:commitstatus", "all", nil}, + {"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,write:commitstatus,public-only", "public-only,all", nil}, } - for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user", "commitstatus"} { 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}, From 14f6e4cad0495d346fd0bcdfd04cc04839054602 Mon Sep 17 00:00:00 2001 From: Aiden Scandella Date: Fri, 29 Nov 2024 11:28:10 -0800 Subject: [PATCH 02/17] start on API perms --- routers/api/v1/api.go | 28 ++++++++++++++++++++++++++-- services/context/api.go | 12 ++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f28ee980e1..e164e607c1 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -432,6 +432,18 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } +// reqRepoCommitStatusWriter user should have a permission to write to commit +// statuses, or write to a repo, or be a site admin +func reqRepoCommitStatusWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + // TODO + if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { + ctx.Error(http.StatusForbidden, "reqRepoCommitStatusWriter", "user should have a permission to write to a repo") + return + } + } +} + // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin func reqRepoBranchWriter(ctx *context.APIContext) { options, ok := web.GetForm(ctx).(api.FileOptionInterface) @@ -451,6 +463,18 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { } } +// reqRepoReader user should have specific commit status read permission, or +// repo read permission, or be a repo admin or a site admin +func reqRepoCommitStatusReader(unitType unit.Type) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + // TODO + if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { + ctx.Error(http.StatusForbidden, "reqRepoCommitStatusReader", "user should have specific read permission or be a repo admin or a site admin") + return + } + } +} + // reqAnyRepoReader user should have any permission to read repository or permissions of site admin func reqAnyRepoReader() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -1323,8 +1347,8 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }, reqRepoReader(unit.TypeCode)) + Post(reqToken(), reqRepoCommitStatusWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }, reqRepoCommitStatusReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.Group("/{ref}", func() { diff --git a/services/context/api.go b/services/context/api.go index b45e80a329..be8e7161e4 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -388,3 +388,15 @@ func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { return false } + +// IsUserRepoWriter returns true if current user has write commit status privilege in current repo +func (ctx *APIContext) IsUserCommitStatusWriter(unitTypes []unit.Type) bool { + for _, unitType := range unitTypes { + // TODO + if ctx.Repo.CanWrite(unitType) { + return true + } + } + + return false +} From ab6c80a133dee780a554de886d5d04ddc17f9f34 Mon Sep 17 00:00:00 2001 From: Aiden Scandella Date: Fri, 29 Nov 2024 13:58:20 -0800 Subject: [PATCH 03/17] add to token creation UI --- web_src/js/components/ScopedAccessTokenSelector.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/components/ScopedAccessTokenSelector.vue b/web_src/js/components/ScopedAccessTokenSelector.vue index 63214d0bf5..e0535a6564 100644 --- a/web_src/js/components/ScopedAccessTokenSelector.vue +++ b/web_src/js/components/ScopedAccessTokenSelector.vue @@ -23,6 +23,7 @@ const categories = computed(() => { 'organization', 'package', 'repository', + 'commitstatus', 'user'); return categories; }); From 35f68afdf6254204dcba8add698fa6ceb577b710 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Fri, 29 Aug 2025 21:31:52 +0000 Subject: [PATCH 04/17] Fix errors now what? --- routers/api/v1/api.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c2ff7d7b45..d83da47981 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -459,22 +459,23 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { // statuses, or write to a repo, or be a site admin func reqRepoCommitStatusWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { - // TODO + // TODO(not7cd) if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.Error(http.StatusForbidden, "reqRepoCommitStatusWriter", "user should have a permission to write to a repo") + ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo") return } } } -// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin -func reqRepoBranchWriter(ctx *context.APIContext) { - options, ok := web.GetForm(ctx).(api.FileOptionInterface) - if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { - ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") - return - } -} +// TODO(not7cd): do I need this? +// // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin +// func reqRepoBranchWriter(ctx *context.APIContext) { +// options, ok := web.GetForm(ctx).(api.FileOptionInterface) +// if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { +// ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch") +// return +// } +// } // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { @@ -490,9 +491,9 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { // repo read permission, or be a repo admin or a site admin func reqRepoCommitStatusReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { - // TODO + // TODO(not7cd) if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.Error(http.StatusForbidden, "reqRepoCommitStatusReader", "user should have specific read permission or be a repo admin or a site admin") + ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin") return } } From 7c5cc63dc61f96ab94eb188e9103f9d1b5d8a493 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 11:49:03 +0000 Subject: [PATCH 05/17] Add UnitCommitStatus --- models/unit/unit.go | 16 ++++++++++++++++ routers/api/v1/api.go | 38 ++------------------------------------ services/context/api.go | 12 ------------ 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/models/unit/unit.go b/models/unit/unit.go index c0560678ca..89740c791c 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -33,6 +33,7 @@ const ( TypeProjects // 8 Projects TypePackages // 9 Packages TypeActions // 10 Actions + TypeCommitStatus // 11 Commit Status // FIXME: TEAM-UNIT-PERMISSION: the team unit "admin" permission's design is not right, when a new unit is added in the future, // admin team won't inherit the correct admin permission for the new unit, need to have a complete fix before adding any new unit. @@ -65,6 +66,7 @@ var ( TypeProjects, TypePackages, TypeActions, + TypeCommitStatus, } // DefaultRepoUnits contains the default unit types @@ -77,8 +79,10 @@ var ( TypeProjects, TypePackages, TypeActions, + TypeCommitStatus, } + // TODO(not7cd): Defaults that need TypeCommitStatus // ForkRepoUnits contains the default unit types for forks DefaultForkRepoUnits = []Type{ TypeCode, @@ -237,6 +241,7 @@ func (u Unit) MaxPerm() perm.AccessMode { } // Enumerate all the units +// TODO(not7cd): Add TypeCommitStatus var ( UnitCode = Unit{ TypeCode, @@ -328,6 +333,16 @@ var ( perm.AccessModeOwner, } + // TODO(not7cd): Just copied this + UnitCommitStatus = Unit{ + TypeCommitStatus, + "repo.commitstatus", + "/statuses", + "commitstatus.unit.desc", + 8, + perm.AccessModeOwner, + } + // Units contains all the units Units = map[Type]Unit{ TypeCode: UnitCode, @@ -340,6 +355,7 @@ var ( TypeProjects: UnitProjects, TypePackages: UnitPackages, TypeActions: UnitActions, + TypeCommitStatus: UnitCommitStatus, } ) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index d83da47981..8255065d1c 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -455,28 +455,6 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } -// reqRepoCommitStatusWriter user should have a permission to write to commit -// statuses, or write to a repo, or be a site admin -func reqRepoCommitStatusWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { - return func(ctx *context.APIContext) { - // TODO(not7cd) - if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo") - return - } - } -} - -// TODO(not7cd): do I need this? -// // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin -// func reqRepoBranchWriter(ctx *context.APIContext) { -// options, ok := web.GetForm(ctx).(api.FileOptionInterface) -// if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { -// ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch") -// return -// } -// } - // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -487,18 +465,6 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { } } -// reqRepoReader user should have specific commit status read permission, or -// repo read permission, or be a repo admin or a site admin -func reqRepoCommitStatusReader(unitType unit.Type) func(ctx *context.APIContext) { - return func(ctx *context.APIContext) { - // TODO(not7cd) - if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin") - return - } - } -} - // reqAnyRepoReader user should have any permission to read repository or permissions of site admin func reqAnyRepoReader() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -1433,8 +1399,8 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), reqRepoCommitStatusWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }, reqRepoCommitStatusReader(unit.TypeCode)) + Post(reqToken(), reqRepoWriter(unit.TypeCommitStatus), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }, reqRepoWriter(unit.TypeCommitStatus)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.Group("/{ref}", func() { diff --git a/services/context/api.go b/services/context/api.go index cc8e4f65ca..ab50a360f4 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -367,15 +367,3 @@ func (ctx *APIContext) IsUserRepoAdmin() bool { func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) } - -// IsUserRepoWriter returns true if current user has write commit status privilege in current repo -func (ctx *APIContext) IsUserCommitStatusWriter(unitTypes []unit.Type) bool { - for _, unitType := range unitTypes { - // TODO - if ctx.Repo.CanWrite(unitType) { - return true - } - } - - return false -} From d9e9f8878df59b0b35ae91c814630b57d315f83c Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 13:13:22 +0000 Subject: [PATCH 06/17] Revert "Add UnitCommitStatus" This reverts commit 7c5cc63dc61f96ab94eb188e9103f9d1b5d8a493. --- models/unit/unit.go | 16 ---------------- routers/api/v1/api.go | 38 ++++++++++++++++++++++++++++++++++++-- services/context/api.go | 12 ++++++++++++ 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/models/unit/unit.go b/models/unit/unit.go index 89740c791c..c0560678ca 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -33,7 +33,6 @@ const ( TypeProjects // 8 Projects TypePackages // 9 Packages TypeActions // 10 Actions - TypeCommitStatus // 11 Commit Status // FIXME: TEAM-UNIT-PERMISSION: the team unit "admin" permission's design is not right, when a new unit is added in the future, // admin team won't inherit the correct admin permission for the new unit, need to have a complete fix before adding any new unit. @@ -66,7 +65,6 @@ var ( TypeProjects, TypePackages, TypeActions, - TypeCommitStatus, } // DefaultRepoUnits contains the default unit types @@ -79,10 +77,8 @@ var ( TypeProjects, TypePackages, TypeActions, - TypeCommitStatus, } - // TODO(not7cd): Defaults that need TypeCommitStatus // ForkRepoUnits contains the default unit types for forks DefaultForkRepoUnits = []Type{ TypeCode, @@ -241,7 +237,6 @@ func (u Unit) MaxPerm() perm.AccessMode { } // Enumerate all the units -// TODO(not7cd): Add TypeCommitStatus var ( UnitCode = Unit{ TypeCode, @@ -333,16 +328,6 @@ var ( perm.AccessModeOwner, } - // TODO(not7cd): Just copied this - UnitCommitStatus = Unit{ - TypeCommitStatus, - "repo.commitstatus", - "/statuses", - "commitstatus.unit.desc", - 8, - perm.AccessModeOwner, - } - // Units contains all the units Units = map[Type]Unit{ TypeCode: UnitCode, @@ -355,7 +340,6 @@ var ( TypeProjects: UnitProjects, TypePackages: UnitPackages, TypeActions: UnitActions, - TypeCommitStatus: UnitCommitStatus, } ) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8255065d1c..d83da47981 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -455,6 +455,28 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } +// reqRepoCommitStatusWriter user should have a permission to write to commit +// statuses, or write to a repo, or be a site admin +func reqRepoCommitStatusWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + // TODO(not7cd) + if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { + ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo") + return + } + } +} + +// TODO(not7cd): do I need this? +// // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin +// func reqRepoBranchWriter(ctx *context.APIContext) { +// options, ok := web.GetForm(ctx).(api.FileOptionInterface) +// if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { +// ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch") +// return +// } +// } + // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -465,6 +487,18 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { } } +// reqRepoReader user should have specific commit status read permission, or +// repo read permission, or be a repo admin or a site admin +func reqRepoCommitStatusReader(unitType unit.Type) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + // TODO(not7cd) + if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { + ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin") + return + } + } +} + // reqAnyRepoReader user should have any permission to read repository or permissions of site admin func reqAnyRepoReader() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -1399,8 +1433,8 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), reqRepoWriter(unit.TypeCommitStatus), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }, reqRepoWriter(unit.TypeCommitStatus)) + Post(reqToken(), reqRepoCommitStatusWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }, reqRepoCommitStatusReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.Group("/{ref}", func() { diff --git a/services/context/api.go b/services/context/api.go index ab50a360f4..cc8e4f65ca 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -367,3 +367,15 @@ func (ctx *APIContext) IsUserRepoAdmin() bool { func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) } + +// IsUserRepoWriter returns true if current user has write commit status privilege in current repo +func (ctx *APIContext) IsUserCommitStatusWriter(unitTypes []unit.Type) bool { + for _, unitType := range unitTypes { + // TODO + if ctx.Repo.CanWrite(unitType) { + return true + } + } + + return false +} From f49e20dec386413b781867f19d59f7444816f9fa Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 13:17:14 +0000 Subject: [PATCH 07/17] Lets start with token --- routers/api/v1/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index d83da47981..c211e34d0a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1433,8 +1433,8 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), reqRepoCommitStatusWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }, reqRepoCommitStatusReader(unit.TypeCode)) + Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.Group("/{ref}", func() { From bb65a90cab56c7dbb37e70b9a3fe85c705d94a09 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 14:08:06 +0000 Subject: [PATCH 08/17] This kinda works, but not really --- routers/api/v1/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c211e34d0a..885a166c01 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1433,7 +1433,7 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus, auth_model.AccessTokenScopeCategoryRepository), bind(api.CreateStatusOption{}), repo.NewCommitStatus) }) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) From f02a372c65566d9964f82da25e044c2e07c2a7df Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 14:13:36 +0000 Subject: [PATCH 09/17] remove unused --- services/context/api.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/services/context/api.go b/services/context/api.go index cc8e4f65ca..ab50a360f4 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -367,15 +367,3 @@ func (ctx *APIContext) IsUserRepoAdmin() bool { func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) } - -// IsUserRepoWriter returns true if current user has write commit status privilege in current repo -func (ctx *APIContext) IsUserCommitStatusWriter(unitTypes []unit.Type) bool { - for _, unitType := range unitTypes { - // TODO - if ctx.Repo.CanWrite(unitType) { - return true - } - } - - return false -} From a399a5ac3fa73e97c2ef938300555527cf3f72e7 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 14:23:20 +0000 Subject: [PATCH 10/17] remove unused in routers/api/v1/api.go --- routers/api/v1/api.go | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 885a166c01..958ccad8a2 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -455,28 +455,6 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } -// reqRepoCommitStatusWriter user should have a permission to write to commit -// statuses, or write to a repo, or be a site admin -func reqRepoCommitStatusWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { - return func(ctx *context.APIContext) { - // TODO(not7cd) - if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.APIError(http.StatusForbidden, "user should have a permission to write to a repo") - return - } - } -} - -// TODO(not7cd): do I need this? -// // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin -// func reqRepoBranchWriter(ctx *context.APIContext) { -// options, ok := web.GetForm(ctx).(api.FileOptionInterface) -// if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { -// ctx.APIError(http.StatusForbidden, "user should have a permission to write to this branch") -// return -// } -// } - // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -487,18 +465,6 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { } } -// reqRepoReader user should have specific commit status read permission, or -// repo read permission, or be a repo admin or a site admin -func reqRepoCommitStatusReader(unitType unit.Type) func(ctx *context.APIContext) { - return func(ctx *context.APIContext) { - // TODO(not7cd) - if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { - ctx.APIError(http.StatusForbidden, "user should have specific read permission or be a repo admin or a site admin") - return - } - } -} - // reqAnyRepoReader user should have any permission to read repository or permissions of site admin func reqAnyRepoReader() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { From ee83b0dba766de11127c5bbe293a122bfcdaf0c0 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sat, 30 Aug 2025 22:16:30 +0000 Subject: [PATCH 11/17] fix test --- models/auth/access_token_scope_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index b0117a6e1e..b753da3e71 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{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user", "commitstatus"}, GetAccessTokenCategories()) + assert.Equal(t, []string{"activitypub", "admin", "commitstatus", "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}, From 1103ccacb1eea80fb401a84b989d741dfd5dbe37 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sun, 31 Aug 2025 10:10:03 +0000 Subject: [PATCH 12/17] bring back unit check --- routers/api/v1/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 958ccad8a2..3ec23ad052 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1399,8 +1399,8 @@ func Routes() *web.Router { }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus, auth_model.AccessTokenScopeCategoryRepository), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }) + Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }, reqRepoReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.Group("/{ref}", func() { From 4e2530c3bb41aff42d0008add7521768bc38557b Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Sun, 31 Aug 2025 10:13:50 +0000 Subject: [PATCH 13/17] commit status contained within --- 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 7086779458..37beb3e8ad 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -123,8 +123,8 @@ const ( accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1< Date: Tue, 20 Jan 2026 23:28:07 +0100 Subject: [PATCH 14/17] verification script --- verify35383.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100755 verify35383.py diff --git a/verify35383.py b/verify35383.py new file mode 100755 index 0000000000..232cde6677 --- /dev/null +++ b/verify35383.py @@ -0,0 +1,88 @@ +# usage: uv run pytest -v verify35383.py + +import os +import time +import requests +import pytest + +GITEA_URL = os.environ["GITEA_URL"].rstrip("/") +OWNER = os.environ["OWNER"] +REPO = os.environ["REPO"] +COMMIT_SHA = os.environ["COMMIT_SHA"] + +STATUS_URL = f"{GITEA_URL}/api/v1/repos/{OWNER}/{REPO}/statuses/{COMMIT_SHA}" + +TOKENS = { + "repo-write": { + "token": os.environ["TOKEN_REPO_WRITE"], + "can_write": True, + "can_read": True, + }, + "repo-read+commitstatus-write": { + "token": os.environ["TOKEN_STATUS_WRITE"], + "can_write": True, + "can_read": True, + }, + "repo-read-only": { + "token": os.environ["TOKEN_REPO_READ"], + "can_write": False, + "can_read": True, + }, +} + + +def auth_headers(token: str) -> dict: + return { + "Authorization": f"token {token}", + "Content-Type": "application/json", + "Accept": "application/json", + } + + +@pytest.mark.parametrize("name,cfg", TOKENS.items()) +def test_commit_status_write_permission(name: str, cfg: dict) -> None: + context = f"perm-test-{name}-{int(time.time())}" + + payload = { + "state": "success", + "context": context, + "description": "Permission verification test", + "target_url": "https://example.com", + } + + response = requests.post( + STATUS_URL, + json=payload, + headers=auth_headers(cfg["token"]), + timeout=10, + ) + + if cfg["can_write"]: + assert response.status_code == 201, response.text + body = response.json() + assert body["status"] == "success" + assert body["context"] == context + else: + assert response.status_code == 403 + + +@pytest.mark.parametrize("name,cfg", TOKENS.items()) +def test_commit_status_read_permission(name: str, cfg: dict) -> None: + response = requests.get( + STATUS_URL, + headers=auth_headers(cfg["token"]), + timeout=10, + ) + + if cfg["can_read"]: + assert response.status_code == 200, response.text + body = response.json() + assert isinstance(body, list) + + if body: + status = body[0] + assert "status" in status + assert "context" in status + assert "created_at" in status + else: + assert response.status_code in (401, 403) From f59e9d3bfbb18e67819633f2d27fbae4106b2636 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Tue, 20 Jan 2026 23:57:25 +0100 Subject: [PATCH 15/17] Fix replace scope to all it seems that the commit status is included somehow in write repo --- models/auth/access_token_scope.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 37beb3e8ad..cf1ab63b67 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -383,7 +383,7 @@ func (bitmap accessTokenScopeBitmap) toScope() AccessTokenScope { scope := AccessTokenScope(strings.Join(scopes, ",")) scope = AccessTokenScope(strings.ReplaceAll( string(scope), - "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user,write:commitstatus", + "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", )) return scope From 657a6ca0e7bd7c167e3d33a6db118f1d9c1b2fbe Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Mon, 2 Feb 2026 23:04:29 +0100 Subject: [PATCH 16/17] the fix --- routers/api/v1/api.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 8ab09bc69c..d16cf3824b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1384,10 +1384,6 @@ func Routes() *web.Router { }) m.Get("/{base}/*", repo.GetPullRequestByBaseHead) }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) - m.Group("/statuses", func() { // "/statuses/{sha}" only accepts commit ID - m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) - }, reqRepoReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) m.PathGroup("/*", func(g *web.RouterPathGroup) { @@ -1457,6 +1453,12 @@ func Routes() *web.Router { }, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) + // + m.Group("/repos/{username}/{reponame}/statuses", func() { // "/statuses/{sha}" only accepts commit ID + m.Combo("/{sha}").Get(repo.GetCommitStatuses). + Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + }, reqRepoReader(unit.TypeCode), repoAssignment(), checkTokenPublicOnly(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryCommitStatus)) + // Artifacts direct download endpoint authenticates via signed url // it is protected by the "sig" parameter (to help to access private repo), so no need to use other middlewares m.Get("/repos/{username}/{reponame}/actions/artifacts/{artifact_id}/zip/raw", repo.DownloadArtifactRaw) From 506f1fababc6b8e0b47ae72b3fbb3c034db83be2 Mon Sep 17 00:00:00 2001 From: Norbert Szulc Date: Tue, 3 Feb 2026 13:22:19 +0100 Subject: [PATCH 17/17] Add comment on commit status permissions Added comment about commit status creation permissions. --- routers/api/v1/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 863ec16d88..b9f3803f08 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1450,7 +1450,7 @@ func Routes() *web.Router { }, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) - // + // Commit status can be created by write:commitstatus or write:repository m.Group("/repos/{username}/{reponame}/statuses", func() { // "/statuses/{sha}" only accepts commit ID m.Combo("/{sha}").Get(repo.GetCommitStatuses). Post(reqToken(), bind(api.CreateStatusOption{}), repo.NewCommitStatus)