mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-14 17:27:39 +02:00
Merge 3a9e828714f6a61eecd88e27e9cc494e4efe8715 into a5d81d9ce230aaa6e1021b6236ca01cb6d2b56c3
This commit is contained in:
commit
fa2136036b
@ -24,6 +24,7 @@ const (
|
||||
AccessTokenScopeCategoryIssue
|
||||
AccessTokenScopeCategoryRepository
|
||||
AccessTokenScopeCategoryUser
|
||||
AccessTokenScopeCategoryCommitStatus
|
||||
)
|
||||
|
||||
// AllAccessTokenScopeCategories contains all access token scope categories
|
||||
@ -37,6 +38,7 @@ var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{
|
||||
AccessTokenScopeCategoryIssue,
|
||||
AccessTokenScopeCategoryRepository,
|
||||
AccessTokenScopeCategoryUser,
|
||||
AccessTokenScopeCategoryCommitStatus,
|
||||
}
|
||||
|
||||
// AccessTokenScopeLevel represents the access levels without a given scope category
|
||||
@ -82,6 +84,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.
|
||||
@ -93,7 +98,7 @@ const (
|
||||
accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits |
|
||||
accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits |
|
||||
accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits |
|
||||
accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits
|
||||
accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits | accessTokenScopeWriteCommitStatusBits
|
||||
|
||||
accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota
|
||||
|
||||
@ -118,12 +123,15 @@ const (
|
||||
accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota
|
||||
accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits
|
||||
|
||||
accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota
|
||||
accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits
|
||||
accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadCommitStatusBits
|
||||
accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits | accessTokenScopeWriteCommitStatusBits
|
||||
|
||||
accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota
|
||||
accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits
|
||||
|
||||
accessTokenScopeReadCommitStatusBits accessTokenScopeBitmap = 1 << iota
|
||||
accessTokenScopeWriteCommitStatusBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadCommitStatusBits
|
||||
|
||||
// The current implementation only supports up to 64 token scopes.
|
||||
// If we need to support > 64 scopes,
|
||||
// refactoring the whole implementation in this file (and only this file) is needed.
|
||||
@ -142,6 +150,7 @@ var allAccessTokenScopes = []AccessTokenScope{
|
||||
AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue,
|
||||
AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository,
|
||||
AccessTokenScopeWriteUser, AccessTokenScopeReadUser,
|
||||
AccessTokenScopeWriteCommitStatus, AccessTokenScopeReadCommitStatus,
|
||||
}
|
||||
|
||||
// allAccessTokenScopeBits contains all access token scopes.
|
||||
@ -166,6 +175,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
|
||||
@ -180,6 +191,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A
|
||||
AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue,
|
||||
AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository,
|
||||
AccessTokenScopeCategoryUser: AccessTokenScopeReadUser,
|
||||
AccessTokenScopeCategoryCommitStatus: AccessTokenScopeReadCommitStatus,
|
||||
},
|
||||
Write: {
|
||||
AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub,
|
||||
@ -191,6 +203,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A
|
||||
AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue,
|
||||
AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository,
|
||||
AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser,
|
||||
AccessTokenScopeCategoryCommitStatus: AccessTokenScopeWriteCommitStatus,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ type scopeTestNormalize struct {
|
||||
}
|
||||
|
||||
func TestAccessTokenScope_Normalize(t *testing.T) {
|
||||
assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, 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},
|
||||
{"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 GetAccessTokenCategories() {
|
||||
|
||||
@ -1373,10 +1373,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(), 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) {
|
||||
@ -1446,6 +1442,12 @@ 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)
|
||||
}, 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)
|
||||
|
||||
88
verify35383.py
Executable file
88
verify35383.py
Executable file
@ -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)
|
||||
Loading…
x
Reference in New Issue
Block a user