diff options
102 files changed, 2182 insertions, 1037 deletions
diff --git a/docs/content/doc/development/oauth2-provider.en-us.md b/docs/content/doc/development/oauth2-provider.en-us.md index 5f9960a477..03833b5ac0 100644 --- a/docs/content/doc/development/oauth2-provider.en-us.md +++ b/docs/content/doc/development/oauth2-provider.en-us.md @@ -44,42 +44,43 @@ To use the Authorization Code Grant as a third party application it is required ## Scopes -Gitea supports the following scopes for tokens: - -| Name | Description | -| ---- | ----------- | -| **(no scope)** | Grants read-only access to public user profile and public repositories. | -| **repo** | Full control over all repositories. | -| **repo:status** | Grants read/write access to commit status in all repositories. | -| **public_repo** | Grants read/write access to public repositories only. | -| **admin:repo_hook** | Grants access to repository hooks of all repositories. This is included in the `repo` scope. | -| **write:repo_hook** | Grants read/write access to repository hooks | -| **read:repo_hook** | Grants read-only access to repository hooks | -| **admin:org** | Grants full access to organization settings | -| **write:org** | Grants read/write access to organization settings | -| **read:org** | Grants read-only access to organization settings | -| **admin:public_key** | Grants full access for managing public keys | -| **write:public_key** | Grant read/write access to public keys | -| **read:public_key** | Grant read-only access to public keys | -| **admin:org_hook** | Grants full access to organizational-level hooks | -| **admin:user_hook** | Grants full access to user-level hooks | -| **notification** | Grants full access to notifications | -| **user** | Grants full access to user profile info | -| **read:user** | Grants read access to user's profile | -| **user:email** | Grants read access to user's email addresses | -| **user:follow** | Grants access to follow/un-follow a user | -| **delete_repo** | Grants access to delete repositories as an admin | -| **package** | Grants full access to hosted packages | -| **write:package** | Grants read/write access to packages | -| **read:package** | Grants read access to packages | -| **delete:package** | Grants delete access to packages | -| **admin:gpg_key** | Grants full access for managing GPG keys | -| **write:gpg_key** | Grants read/write access to GPG keys | -| **read:gpg_key** | Grants read-only access to GPG keys | -| **admin:application** | Grants full access to manage applications | -| **write:application** | Grants read/write access for managing applications | -| **read:application** | Grants read access for managing applications | -| **sudo** | Allows to perform actions as the site admin. | +Gitea supports scoped access tokens, which allow users the ability to restrict tokens to operate only on selected url routes. Scopes are grouped by high-level API routes, and further refined to the following: + +- `read`: `GET` routes +- `write`: `POST`, `PUT`, `PATCH`, and `DELETE` routes (in addition to `GET`) + +Gitea token scopes are as follows: + +| Name | Description | +| ---- |--------------------------------------------------------------------------------------------------------------------------------------------------| +| **(no scope)** | Not supported. A scope is required even for public repositories. | +| **activitypub** | `activitypub` API routes: ActivityPub related operations. | +| **read:activitypub** | Grants read access for ActivityPub operations. | +| **write:activitypub** | Grants read/write/delete access for ActivityPub operations. | +| **admin** | `/admin/*` API routes: Site-wide administrative operations (hidden for non-admin accounts). | +| **read:admin** | Grants read access for admin operations, such as getting cron jobs or registered user emails. | +| **write:admin** | Grants read/write/delete access for admin operations, such as running cron jobs or updating user accounts. | | +| **issue** | `issues/*`, `labels/*`, `milestones/*` API routes: Issue-related operations. | +| **read:issue** | Grants read access for issues operations, such as getting issue comments, issue attachments, and milestones. | +| **write:issue** | Grants read/write/delete access for issues operations, such as posting or editing an issue comment or attachment, and updating milestones. | +| **misc** | miscellaneous and settings top-level API routes. | +| **read:misc** | Grants read access to miscellaneous operations, such as getting label and gitignore templates. | +| **write:misc** | Grants read/write/delete access to miscellaneous operations, such as markup utility operations. | +| **notification** | `notification/*` API routes: user notification operations. | +| **read:notification** | Grants read access to user notifications, such as which notifications users are subscribed to and read new notifications. | +| **write:notification** | Grants read/write/delete access to user notifications, such as marking notifications as read. | +| **organization** | `orgs/*` and `teams/*` API routes: Organization and team management operations. | +| **read:organization** | Grants read access to org and team status, such as listing all orgs a user has visibility to, teams, and team members. | +| **write:organization** | Grants read/write/delete access to org and team status, such as creating and updating teams and updating org settings. | +| **package** | `/packages/*` API routes: Packages operations | +| **read:package** | Grants read access to package operations, such as reading and downloading available packages. | +| **write:package** | Grants read/write/delete access to package operations. Currently the same as `read:package`. | +| **repository** | `/repos/*` API routes except `/repos/issues/*`: Repository file, pull-request, and release operations. | +| **read:repository** | Grants read access to repository operations, such as getting repository files, releases, collaborators. | +| **write:repository** | Grants read/write/delete access to repository operations, such as getting updating repository files, creating pull requests, updating collaborators. | +| **user** | `/user/*` and `/users/*` API routes: User-related operations. | +| **read:user** | Grants read access to user operations, such as getting user repo subscriptions and user settings. | +| **write:user** | Grants read/write/delete access to user operations, such as updating user repo subscriptions, followed users, and user settings. | ## Client types diff --git a/models/auth/token.go b/models/auth/token.go index 3f9f117f73..9374fe38c2 100644 --- a/models/auth/token.go +++ b/models/auth/token.go @@ -112,6 +112,15 @@ func NewAccessToken(t *AccessToken) error { return err } +// DisplayPublicOnly whether to display this as a public-only token. +func (t *AccessToken) DisplayPublicOnly() bool { + publicOnly, err := t.Scope.PublicOnly() + if err != nil { + return false + } + return publicOnly +} + func getAccessTokenIDFromCache(token string) int64 { if successfulAccessTokenCache == nil { return 0 diff --git a/models/auth/token_scope.go b/models/auth/token_scope.go index 06c89fecc2..61e684ea27 100644 --- a/models/auth/token_scope.go +++ b/models/auth/token_scope.go @@ -6,113 +6,122 @@ package auth import ( "fmt" "strings" + + "code.gitea.io/gitea/models/perm" ) -// AccessTokenScope represents the scope for an access token. -type AccessTokenScope string +// AccessTokenScopeCategory represents the scope category for an access token +type AccessTokenScopeCategory int const ( - AccessTokenScopeAll AccessTokenScope = "all" + AccessTokenScopeCategoryActivityPub = iota + AccessTokenScopeCategoryAdmin + AccessTokenScopeCategoryMisc + AccessTokenScopeCategoryNotification + AccessTokenScopeCategoryOrganization + AccessTokenScopeCategoryPackage + AccessTokenScopeCategoryIssue + AccessTokenScopeCategoryRepository + AccessTokenScopeCategoryUser +) - AccessTokenScopeRepo AccessTokenScope = "repo" - AccessTokenScopeRepoStatus AccessTokenScope = "repo:status" - AccessTokenScopePublicRepo AccessTokenScope = "public_repo" +// AllAccessTokenScopeCategories contains all access token scope categories +var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{ + AccessTokenScopeCategoryActivityPub, + AccessTokenScopeCategoryAdmin, + AccessTokenScopeCategoryMisc, + AccessTokenScopeCategoryNotification, + AccessTokenScopeCategoryOrganization, + AccessTokenScopeCategoryPackage, + AccessTokenScopeCategoryIssue, + AccessTokenScopeCategoryRepository, + AccessTokenScopeCategoryUser, +} - AccessTokenScopeAdminOrg AccessTokenScope = "admin:org" - AccessTokenScopeWriteOrg AccessTokenScope = "write:org" - AccessTokenScopeReadOrg AccessTokenScope = "read:org" +// AccessTokenScopeLevel represents the access levels without a given scope category +type AccessTokenScopeLevel int - AccessTokenScopeAdminPublicKey AccessTokenScope = "admin:public_key" - AccessTokenScopeWritePublicKey AccessTokenScope = "write:public_key" - AccessTokenScopeReadPublicKey AccessTokenScope = "read:public_key" +const ( + NoAccess AccessTokenScopeLevel = iota + Read + Write +) + +// AccessTokenScope represents the scope for an access token. +type AccessTokenScope string - AccessTokenScopeAdminRepoHook AccessTokenScope = "admin:repo_hook" - AccessTokenScopeWriteRepoHook AccessTokenScope = "write:repo_hook" - AccessTokenScopeReadRepoHook AccessTokenScope = "read:repo_hook" +// for all categories, write implies read +const ( + AccessTokenScopeAll AccessTokenScope = "all" + AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos - AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook" + AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub" + AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub" - AccessTokenScopeAdminUserHook AccessTokenScope = "admin:user_hook" + AccessTokenScopeReadAdmin AccessTokenScope = "read:admin" + AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin" - AccessTokenScopeNotification AccessTokenScope = "notification" + AccessTokenScopeReadMisc AccessTokenScope = "read:misc" + AccessTokenScopeWriteMisc AccessTokenScope = "write:misc" - AccessTokenScopeUser AccessTokenScope = "user" - AccessTokenScopeReadUser AccessTokenScope = "read:user" - AccessTokenScopeUserEmail AccessTokenScope = "user:email" - AccessTokenScopeUserFollow AccessTokenScope = "user:follow" + AccessTokenScopeReadNotification AccessTokenScope = "read:notification" + AccessTokenScopeWriteNotification AccessTokenScope = "write:notification" - AccessTokenScopeDeleteRepo AccessTokenScope = "delete_repo" + AccessTokenScopeReadOrganization AccessTokenScope = "read:organization" + AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization" - AccessTokenScopePackage AccessTokenScope = "package" - AccessTokenScopeWritePackage AccessTokenScope = "write:package" - AccessTokenScopeReadPackage AccessTokenScope = "read:package" - AccessTokenScopeDeletePackage AccessTokenScope = "delete:package" + AccessTokenScopeReadPackage AccessTokenScope = "read:package" + AccessTokenScopeWritePackage AccessTokenScope = "write:package" - AccessTokenScopeAdminGPGKey AccessTokenScope = "admin:gpg_key" - AccessTokenScopeWriteGPGKey AccessTokenScope = "write:gpg_key" - AccessTokenScopeReadGPGKey AccessTokenScope = "read:gpg_key" + AccessTokenScopeReadIssue AccessTokenScope = "read:issue" + AccessTokenScopeWriteIssue AccessTokenScope = "write:issue" - AccessTokenScopeAdminApplication AccessTokenScope = "admin:application" - AccessTokenScopeWriteApplication AccessTokenScope = "write:application" - AccessTokenScopeReadApplication AccessTokenScope = "read:application" + AccessTokenScopeReadRepository AccessTokenScope = "read:repository" + AccessTokenScopeWriteRepository AccessTokenScope = "write:repository" - AccessTokenScopeSudo AccessTokenScope = "sudo" + AccessTokenScopeReadUser AccessTokenScope = "read:user" + AccessTokenScopeWriteUser AccessTokenScope = "write:user" ) -// AccessTokenScopeBitmap represents a bitmap of access token scopes. -type AccessTokenScopeBitmap uint64 +// accessTokenScopeBitmap represents a bitmap of access token scopes. +type accessTokenScopeBitmap uint64 // Bitmap of each scope, including the child scopes. const ( - // AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`. - AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits | - AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits | AccessTokenScopeAdminUserHookBits | - AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits | - AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits - - AccessTokenScopeRepoBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeRepoStatusBits | AccessTokenScopePublicRepoBits | AccessTokenScopeAdminRepoHookBits - AccessTokenScopeRepoStatusBits AccessTokenScopeBitmap = 1 << iota - AccessTokenScopePublicRepoBits AccessTokenScopeBitmap = 1 << iota - - AccessTokenScopeAdminOrgBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWriteOrgBits - AccessTokenScopeWriteOrgBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadOrgBits - AccessTokenScopeReadOrgBits AccessTokenScopeBitmap = 1 << iota - - AccessTokenScopeAdminPublicKeyBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWritePublicKeyBits - AccessTokenScopeWritePublicKeyBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadPublicKeyBits - AccessTokenScopeReadPublicKeyBits AccessTokenScopeBitmap = 1 << iota + // AccessTokenScopeAllBits is the bitmap of all access token scopes + accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits | + accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits | + accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits | + accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits - AccessTokenScopeAdminRepoHookBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWriteRepoHookBits - AccessTokenScopeWriteRepoHookBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadRepoHookBits - AccessTokenScopeReadRepoHookBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota - AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadActivityPubBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits - AccessTokenScopeAdminUserHookBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadAdminBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits - AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadMiscBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits - AccessTokenScopeUserBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits - AccessTokenScopeReadUserBits AccessTokenScopeBitmap = 1 << iota - AccessTokenScopeUserEmailBits AccessTokenScopeBitmap = 1 << iota - AccessTokenScopeUserFollowBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadNotificationBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits - AccessTokenScopeDeleteRepoBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadOrganizationBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits - AccessTokenScopePackageBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWritePackageBits | AccessTokenScopeDeletePackageBits - AccessTokenScopeWritePackageBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadPackageBits - AccessTokenScopeReadPackageBits AccessTokenScopeBitmap = 1 << iota - AccessTokenScopeDeletePackageBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadPackageBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits - AccessTokenScopeAdminGPGKeyBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWriteGPGKeyBits - AccessTokenScopeWriteGPGKeyBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadGPGKeyBits - AccessTokenScopeReadGPGKeyBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits - AccessTokenScopeAdminApplicationBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeWriteApplicationBits - AccessTokenScopeWriteApplicationBits AccessTokenScopeBitmap = 1<<iota | AccessTokenScopeReadApplicationBits - AccessTokenScopeReadApplicationBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits - AccessTokenScopeSudoBits AccessTokenScopeBitmap = 1 << iota + accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits // The current implementation only supports up to 64 token scopes. // If we need to support > 64 scopes, @@ -120,61 +129,110 @@ const ( ) // allAccessTokenScopes contains all access token scopes. -// The order is important: parent scope must precedes child scopes. +// The order is important: parent scope must precede child scopes. var allAccessTokenScopes = []AccessTokenScope{ - AccessTokenScopeRepo, AccessTokenScopeRepoStatus, AccessTokenScopePublicRepo, - AccessTokenScopeAdminOrg, AccessTokenScopeWriteOrg, AccessTokenScopeReadOrg, - AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey, - AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook, - AccessTokenScopeAdminOrgHook, - AccessTokenScopeAdminUserHook, - AccessTokenScopeNotification, - AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow, - AccessTokenScopeDeleteRepo, - AccessTokenScopePackage, AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, AccessTokenScopeDeletePackage, - AccessTokenScopeAdminGPGKey, AccessTokenScopeWriteGPGKey, AccessTokenScopeReadGPGKey, - AccessTokenScopeAdminApplication, AccessTokenScopeWriteApplication, AccessTokenScopeReadApplication, - AccessTokenScopeSudo, + AccessTokenScopePublicOnly, + AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub, + AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin, + AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc, + AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification, + AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization, + AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, + AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue, + AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository, + AccessTokenScopeWriteUser, AccessTokenScopeReadUser, } // allAccessTokenScopeBits contains all access token scopes. -var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{ - AccessTokenScopeRepo: AccessTokenScopeRepoBits, - AccessTokenScopeRepoStatus: AccessTokenScopeRepoStatusBits, - AccessTokenScopePublicRepo: AccessTokenScopePublicRepoBits, - AccessTokenScopeAdminOrg: AccessTokenScopeAdminOrgBits, - AccessTokenScopeWriteOrg: AccessTokenScopeWriteOrgBits, - AccessTokenScopeReadOrg: AccessTokenScopeReadOrgBits, - AccessTokenScopeAdminPublicKey: AccessTokenScopeAdminPublicKeyBits, - AccessTokenScopeWritePublicKey: AccessTokenScopeWritePublicKeyBits, - AccessTokenScopeReadPublicKey: AccessTokenScopeReadPublicKeyBits, - AccessTokenScopeAdminRepoHook: AccessTokenScopeAdminRepoHookBits, - AccessTokenScopeWriteRepoHook: AccessTokenScopeWriteRepoHookBits, - AccessTokenScopeReadRepoHook: AccessTokenScopeReadRepoHookBits, - AccessTokenScopeAdminOrgHook: AccessTokenScopeAdminOrgHookBits, - AccessTokenScopeAdminUserHook: AccessTokenScopeAdminUserHookBits, - AccessTokenScopeNotification: AccessTokenScopeNotificationBits, - AccessTokenScopeUser: AccessTokenScopeUserBits, - AccessTokenScopeReadUser: AccessTokenScopeReadUserBits, - AccessTokenScopeUserEmail: AccessTokenScopeUserEmailBits, - AccessTokenScopeUserFollow: AccessTokenScopeUserFollowBits, - AccessTokenScopeDeleteRepo: AccessTokenScopeDeleteRepoBits, - AccessTokenScopePackage: AccessTokenScopePackageBits, - AccessTokenScopeWritePackage: AccessTokenScopeWritePackageBits, - AccessTokenScopeReadPackage: AccessTokenScopeReadPackageBits, - AccessTokenScopeDeletePackage: AccessTokenScopeDeletePackageBits, - AccessTokenScopeAdminGPGKey: AccessTokenScopeAdminGPGKeyBits, - AccessTokenScopeWriteGPGKey: AccessTokenScopeWriteGPGKeyBits, - AccessTokenScopeReadGPGKey: AccessTokenScopeReadGPGKeyBits, - AccessTokenScopeAdminApplication: AccessTokenScopeAdminApplicationBits, - AccessTokenScopeWriteApplication: AccessTokenScopeWriteApplicationBits, - AccessTokenScopeReadApplication: AccessTokenScopeReadApplicationBits, - AccessTokenScopeSudo: AccessTokenScopeSudoBits, +var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{ + AccessTokenScopeAll: accessTokenScopeAllBits, + AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits, + AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits, + AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits, + AccessTokenScopeReadAdmin: accessTokenScopeReadAdminBits, + AccessTokenScopeWriteAdmin: accessTokenScopeWriteAdminBits, + AccessTokenScopeReadMisc: accessTokenScopeReadMiscBits, + AccessTokenScopeWriteMisc: accessTokenScopeWriteMiscBits, + AccessTokenScopeReadNotification: accessTokenScopeReadNotificationBits, + AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits, + AccessTokenScopeReadOrganization: accessTokenScopeReadOrganizationBits, + AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits, + AccessTokenScopeReadPackage: accessTokenScopeReadPackageBits, + AccessTokenScopeWritePackage: accessTokenScopeWritePackageBits, + AccessTokenScopeReadIssue: accessTokenScopeReadIssueBits, + AccessTokenScopeWriteIssue: accessTokenScopeWriteIssueBits, + AccessTokenScopeReadRepository: accessTokenScopeReadRepositoryBits, + AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits, + AccessTokenScopeReadUser: accessTokenScopeReadUserBits, + AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits, +} + +// readAccessTokenScopes maps a scope category to the read permission scope +var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]AccessTokenScope{ + Read: { + AccessTokenScopeCategoryActivityPub: AccessTokenScopeReadActivityPub, + AccessTokenScopeCategoryAdmin: AccessTokenScopeReadAdmin, + AccessTokenScopeCategoryMisc: AccessTokenScopeReadMisc, + AccessTokenScopeCategoryNotification: AccessTokenScopeReadNotification, + AccessTokenScopeCategoryOrganization: AccessTokenScopeReadOrganization, + AccessTokenScopeCategoryPackage: AccessTokenScopeReadPackage, + AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue, + AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository, + AccessTokenScopeCategoryUser: AccessTokenScopeReadUser, + }, + Write: { + AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub, + AccessTokenScopeCategoryAdmin: AccessTokenScopeWriteAdmin, + AccessTokenScopeCategoryMisc: AccessTokenScopeWriteMisc, + AccessTokenScopeCategoryNotification: AccessTokenScopeWriteNotification, + AccessTokenScopeCategoryOrganization: AccessTokenScopeWriteOrganization, + AccessTokenScopeCategoryPackage: AccessTokenScopeWritePackage, + AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue, + AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository, + AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser, + }, +} + +// GetRequiredScopes gets the specific scopes for a given level and categories +func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope { + scopes := make([]AccessTokenScope, 0, len(scopeCategories)) + for _, cat := range scopeCategories { + scopes = append(scopes, accessTokenScopes[level][cat]) + } + return scopes } -// Parse parses the scope string into a bitmap, thus removing possible duplicates. -func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) { - var bitmap AccessTokenScopeBitmap +// ContainsCategory checks if a list of categories contains a specific category +func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool { + for _, c := range categories { + if c == category { + return true + } + } + return false +} + +// GetScopeLevelFromAccessMode converts permission access mode to scope level +func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel { + switch mode { + case perm.AccessModeNone: + return NoAccess + case perm.AccessModeRead: + return Read + case perm.AccessModeWrite: + return Write + case perm.AccessModeAdmin: + return Write + case perm.AccessModeOwner: + return Write + default: + return NoAccess + } +} + +// parse the scope string into a bitmap, thus removing possible duplicates. +func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) { + var bitmap accessTokenScopeBitmap // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code remainingScopes := string(s) @@ -196,7 +254,7 @@ func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) { continue } if singleScope == AccessTokenScopeAll { - bitmap |= AccessTokenScopeAllBits + bitmap |= accessTokenScopeAllBits continue } @@ -217,26 +275,42 @@ func (s AccessTokenScope) StringSlice() []string { // Normalize returns a normalized scope string without any duplicates. func (s AccessTokenScope) Normalize() (AccessTokenScope, error) { - bitmap, err := s.Parse() + bitmap, err := s.parse() if err != nil { return "", err } - return bitmap.ToScope(), nil + return bitmap.toScope(), nil } -// HasScope returns true if the string has the given scope -func (s AccessTokenScope) HasScope(scope AccessTokenScope) (bool, error) { - bitmap, err := s.Parse() +// PublicOnly checks if this token scope is limited to public resources +func (s AccessTokenScope) PublicOnly() (bool, error) { + bitmap, err := s.parse() if err != nil { return false, err } - return bitmap.HasScope(scope) + return bitmap.hasScope(AccessTokenScopePublicOnly) } // HasScope returns true if the string has the given scope -func (bitmap AccessTokenScopeBitmap) HasScope(scope AccessTokenScope) (bool, error) { +func (s AccessTokenScope) HasScope(scopes ...AccessTokenScope) (bool, error) { + bitmap, err := s.parse() + if err != nil { + return false, err + } + + for _, s := range scopes { + if has, err := bitmap.hasScope(s); !has || err != nil { + return has, err + } + } + + return true, nil +} + +// hasScope returns true if the string has the given scope +func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) { expectedBits, ok := allAccessTokenScopeBits[scope] if !ok { return false, fmt.Errorf("invalid access token scope: %s", scope) @@ -245,17 +319,17 @@ func (bitmap AccessTokenScopeBitmap) HasScope(scope AccessTokenScope) (bool, err return bitmap&expectedBits == expectedBits, nil } -// ToScope returns a normalized scope string without any duplicates. -func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope { +// toScope returns a normalized scope string without any duplicates. +func (bitmap accessTokenScopeBitmap) toScope() AccessTokenScope { var scopes []string // iterate over all scopes, and reconstruct the bitmap // if the reconstructed bitmap doesn't change, then the scope is already included - var reconstruct AccessTokenScopeBitmap + var reconstruct accessTokenScopeBitmap for _, singleScope := range allAccessTokenScopes { // no need for error checking here, since we know the scope is valid - if ok, _ := bitmap.HasScope(singleScope); ok { + if ok, _ := bitmap.hasScope(singleScope); ok { current := reconstruct | allAccessTokenScopeBits[singleScope] if current == reconstruct { continue @@ -269,7 +343,7 @@ func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope { scope := AccessTokenScope(strings.Join(scopes, ",")) scope = AccessTokenScope(strings.ReplaceAll( string(scope), - "repo,admin:org,admin:public_key,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", + "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user", "all", )) return scope diff --git a/models/auth/token_scope_test.go b/models/auth/token_scope_test.go index b96a5fd469..a6097e45d7 100644 --- a/models/auth/token_scope_test.go +++ b/models/auth/token_scope_test.go @@ -4,44 +4,35 @@ package auth import ( + "fmt" "testing" "github.com/stretchr/testify/assert" ) +type scopeTestNormalize struct { + in AccessTokenScope + out AccessTokenScope + err error +} + func TestAccessTokenScope_Normalize(t *testing.T) { - tests := []struct { - in AccessTokenScope - out AccessTokenScope - err error - }{ + tests := []scopeTestNormalize{ {"", "", nil}, - {"repo", "repo", nil}, - {"repo,repo:status", "repo", nil}, - {"repo,public_repo", "repo", nil}, - {"admin:public_key,write:public_key", "admin:public_key", nil}, - {"admin:public_key,read:public_key", "admin:public_key", nil}, - {"write:public_key,read:public_key", "write:public_key", nil}, // read is include in write - {"admin:repo_hook,write:repo_hook", "admin:repo_hook", nil}, - {"admin:repo_hook,read:repo_hook", "admin:repo_hook", nil}, - {"repo,admin:repo_hook,read:repo_hook", "repo", nil}, // admin:repo_hook is a child scope of repo - {"repo,read:repo_hook", "repo", nil}, // read:repo_hook is a child scope of repo - {"user", "user", nil}, - {"user,read:user", "user", nil}, - {"user,admin:org,write:org", "admin:org,user", nil}, - {"admin:org,write:org,user", "admin:org,user", nil}, - {"package", "package", nil}, - {"package,write:package", "package", nil}, - {"package,write:package,delete:package", "package", nil}, - {"write:package,read:package", "write:package", nil}, // read is include in write - {"write:package,delete:package", "write:package,delete:package", nil}, // write and delete are not include in each other - {"admin:gpg_key", "admin:gpg_key", nil}, - {"admin:gpg_key,write:gpg_key", "admin:gpg_key", nil}, - {"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil}, - {"admin:application,write:application,user", "user,admin:application", nil}, + {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, {"all", "all", nil}, - {"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil}, - {"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,admin:user_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", 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}, + } + + for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + 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}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%[1]s,read:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s,write:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, + ) } for _, test := range tests { @@ -53,31 +44,46 @@ func TestAccessTokenScope_Normalize(t *testing.T) { } } +type scopeTestHasScope struct { + in AccessTokenScope + scope AccessTokenScope + out bool + err error +} + func TestAccessTokenScope_HasScope(t *testing.T) { - tests := []struct { - in AccessTokenScope - scope AccessTokenScope - out bool - err error - }{ - {"repo", "repo", true, nil}, - {"repo", "repo:status", true, nil}, - {"repo", "public_repo", true, nil}, - {"repo", "admin:org", false, nil}, - {"repo", "admin:public_key", false, nil}, - {"repo:status", "repo", false, nil}, - {"repo:status", "public_repo", false, nil}, - {"admin:org", "write:org", true, nil}, - {"admin:org", "read:org", true, nil}, - {"admin:org", "admin:org", true, nil}, - {"user", "read:user", true, nil}, - {"package", "write:package", true, nil}, + tests := []scopeTestHasScope{ + {"read:admin", "write:package", false, nil}, + {"all", "write:package", true, nil}, + {"write:package", "all", false, nil}, + {"public-only", "read:issue", false, nil}, + } + + for _, scope := range []string{"activitypub", "admin", "misc", "notification", "organization", "package", "issue", "repository", "user"} { + tests = append(tests, + scopeTestHasScope{ + AccessTokenScope(fmt.Sprintf("read:%s", scope)), + AccessTokenScope(fmt.Sprintf("read:%s", scope)), true, nil, + }, + scopeTestHasScope{ + AccessTokenScope(fmt.Sprintf("write:%s", scope)), + AccessTokenScope(fmt.Sprintf("write:%s", scope)), true, nil, + }, + scopeTestHasScope{ + AccessTokenScope(fmt.Sprintf("write:%s", scope)), + AccessTokenScope(fmt.Sprintf("read:%s", scope)), true, nil, + }, + scopeTestHasScope{ + AccessTokenScope(fmt.Sprintf("read:%s", scope)), + AccessTokenScope(fmt.Sprintf("write:%s", scope)), false, nil, + }, + ) } for _, test := range tests { t.Run(string(test.in), func(t *testing.T) { - scope, err := test.in.HasScope(test.scope) - assert.Equal(t, test.out, scope) + hasScope, err := test.in.HasScope(test.scope) + assert.Equal(t, test.out, hasScope) assert.Equal(t, test.err, err) }) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 231c93cc74..d96c17bfb5 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -495,6 +495,8 @@ var migrations = []Migration{ NewMigration("Add Actions Artifact table", v1_20.CreateActionArtifactTable), // v258 -> 259 NewMigration("Add PinOrder Column", v1_20.AddPinOrderToIssue), + // v259 -> 260 + NewMigration("Convert scoped access tokens", v1_20.ConvertScopedAccessTokens), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_20/main_test.go b/models/migrations/v1_20/main_test.go new file mode 100644 index 0000000000..92a1a9f622 --- /dev/null +++ b/models/migrations/v1_20/main_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" +) + +func TestMain(m *testing.M) { + base.MainTest(m) +} diff --git a/models/migrations/v1_20/v259.go b/models/migrations/v1_20/v259.go new file mode 100644 index 0000000000..5b8ced4ad7 --- /dev/null +++ b/models/migrations/v1_20/v259.go @@ -0,0 +1,360 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "fmt" + "strings" + + "code.gitea.io/gitea/modules/log" + + "xorm.io/xorm" +) + +// unknownAccessTokenScope represents the scope for an access token that isn't +// known be an old token or a new token. +type unknownAccessTokenScope string + +// AccessTokenScope represents the scope for an access token. +type AccessTokenScope string + +// for all categories, write implies read +const ( + AccessTokenScopeAll AccessTokenScope = "all" + AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos + + AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub" + AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub" + + AccessTokenScopeReadAdmin AccessTokenScope = "read:admin" + AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin" + + AccessTokenScopeReadMisc AccessTokenScope = "read:misc" + AccessTokenScopeWriteMisc AccessTokenScope = "write:misc" + + AccessTokenScopeReadNotification AccessTokenScope = "read:notification" + AccessTokenScopeWriteNotification AccessTokenScope = "write:notification" + + AccessTokenScopeReadOrganization AccessTokenScope = "read:organization" + AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization" + + AccessTokenScopeReadPackage AccessTokenScope = "read:package" + AccessTokenScopeWritePackage AccessTokenScope = "write:package" + + AccessTokenScopeReadIssue AccessTokenScope = "read:issue" + AccessTokenScopeWriteIssue AccessTokenScope = "write:issue" + + AccessTokenScopeReadRepository AccessTokenScope = "read:repository" + AccessTokenScopeWriteRepository AccessTokenScope = "write:repository" + + AccessTokenScopeReadUser AccessTokenScope = "read:user" + AccessTokenScopeWriteUser AccessTokenScope = "write:user" +) + +// accessTokenScopeBitmap represents a bitmap of access token scopes. +type accessTokenScopeBitmap uint64 + +// Bitmap of each scope, including the child scopes. +const ( + // AccessTokenScopeAllBits is the bitmap of all access token scopes + accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits | + accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits | + accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits | + accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits + + accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota + + accessTokenScopeReadActivityPubBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits + + accessTokenScopeReadAdminBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits + + accessTokenScopeReadMiscBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits + + accessTokenScopeReadNotificationBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits + + accessTokenScopeReadOrganizationBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits + + accessTokenScopeReadPackageBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits + + accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits + + accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits + + accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota + accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits + + // 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. +) + +// allAccessTokenScopes contains all access token scopes. +// The order is important: parent scope must precede child scopes. +var allAccessTokenScopes = []AccessTokenScope{ + AccessTokenScopePublicOnly, + AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub, + AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin, + AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc, + AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification, + AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization, + AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, + AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue, + AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository, + AccessTokenScopeWriteUser, AccessTokenScopeReadUser, +} + +// allAccessTokenScopeBits contains all access token scopes. +var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{ + AccessTokenScopeAll: accessTokenScopeAllBits, + AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits, + AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits, + AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits, + AccessTokenScopeReadAdmin: accessTokenScopeReadAdminBits, + AccessTokenScopeWriteAdmin: accessTokenScopeWriteAdminBits, + AccessTokenScopeReadMisc: accessTokenScopeReadMiscBits, + AccessTokenScopeWriteMisc: accessTokenScopeWriteMiscBits, + AccessTokenScopeReadNotification: accessTokenScopeReadNotificationBits, + AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits, + AccessTokenScopeReadOrganization: accessTokenScopeReadOrganizationBits, + AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits, + AccessTokenScopeReadPackage: accessTokenScopeReadPackageBits, + AccessTokenScopeWritePackage: accessTokenScopeWritePackageBits, + AccessTokenScopeReadIssue: accessTokenScopeReadIssueBits, + AccessTokenScopeWriteIssue: accessTokenScopeWriteIssueBits, + AccessTokenScopeReadRepository: accessTokenScopeReadRepositoryBits, + AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits, + AccessTokenScopeReadUser: accessTokenScopeReadUserBits, + AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits, +} + +// hasScope returns true if the string has the given scope +func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) { + expectedBits, ok := allAccessTokenScopeBits[scope] + if !ok { + return false, fmt.Errorf("invalid access token scope: %s", scope) + } + + return bitmap&expectedBits == expectedBits, nil +} + +// toScope returns a normalized scope string without any duplicates. +func (bitmap accessTokenScopeBitmap) toScope(unknownScopes *[]unknownAccessTokenScope) AccessTokenScope { + var scopes []string + + // Preserve unknown scopes, and put them at the beginning so that it's clear + // when debugging. + if unknownScopes != nil { + for _, unknownScope := range *unknownScopes { + scopes = append(scopes, string(unknownScope)) + } + } + + // iterate over all scopes, and reconstruct the bitmap + // if the reconstructed bitmap doesn't change, then the scope is already included + var reconstruct accessTokenScopeBitmap + + for _, singleScope := range allAccessTokenScopes { + // no need for error checking here, since we know the scope is valid + if ok, _ := bitmap.hasScope(singleScope); ok { + current := reconstruct | allAccessTokenScopeBits[singleScope] + if current == reconstruct { + continue + } + + reconstruct = current + scopes = append(scopes, string(singleScope)) + } + } + + 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", + "all", + )) + return scope +} + +// parse the scope string into a bitmap, thus removing possible duplicates. +func (s AccessTokenScope) parse() (accessTokenScopeBitmap, *[]unknownAccessTokenScope) { + var bitmap accessTokenScopeBitmap + var unknownScopes []unknownAccessTokenScope + + // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code + remainingScopes := string(s) + for len(remainingScopes) > 0 { + i := strings.IndexByte(remainingScopes, ',') + var v string + if i < 0 { + v = remainingScopes + remainingScopes = "" + } else if i+1 >= len(remainingScopes) { + v = remainingScopes[:i] + remainingScopes = "" + } else { + v = remainingScopes[:i] + remainingScopes = remainingScopes[i+1:] + } + singleScope := AccessTokenScope(v) + if singleScope == "" { + continue + } + if singleScope == AccessTokenScopeAll { + bitmap |= accessTokenScopeAllBits + continue + } + + bits, ok := allAccessTokenScopeBits[singleScope] + if !ok { + unknownScopes = append(unknownScopes, unknownAccessTokenScope(string(singleScope))) + } + bitmap |= bits + } + + return bitmap, &unknownScopes +} + +// NormalizePreservingUnknown returns a normalized scope string without any +// duplicates. Unknown scopes are included. +func (s AccessTokenScope) NormalizePreservingUnknown() AccessTokenScope { + bitmap, unknownScopes := s.parse() + + return bitmap.toScope(unknownScopes) +} + +// OldAccessTokenScope represents the scope for an access token. +type OldAccessTokenScope string + +const ( + OldAccessTokenScopeAll OldAccessTokenScope = "all" + + OldAccessTokenScopeRepo OldAccessTokenScope = "repo" + OldAccessTokenScopeRepoStatus OldAccessTokenScope = "repo:status" + OldAccessTokenScopePublicRepo OldAccessTokenScope = "public_repo" + + OldAccessTokenScopeAdminOrg OldAccessTokenScope = "admin:org" + OldAccessTokenScopeWriteOrg OldAccessTokenScope = "write:org" + OldAccessTokenScopeReadOrg OldAccessTokenScope = "read:org" + + OldAccessTokenScopeAdminPublicKey OldAccessTokenScope = "admin:public_key" + OldAccessTokenScopeWritePublicKey OldAccessTokenScope = "write:public_key" + OldAccessTokenScopeReadPublicKey OldAccessTokenScope = "read:public_key" + + OldAccessTokenScopeAdminRepoHook OldAccessTokenScope = "admin:repo_hook" + OldAccessTokenScopeWriteRepoHook OldAccessTokenScope = "write:repo_hook" + OldAccessTokenScopeReadRepoHook OldAccessTokenScope = "read:repo_hook" + + OldAccessTokenScopeAdminOrgHook OldAccessTokenScope = "admin:org_hook" + + OldAccessTokenScopeNotification OldAccessTokenScope = "notification" + + OldAccessTokenScopeUser OldAccessTokenScope = "user" + OldAccessTokenScopeReadUser OldAccessTokenScope = "read:user" + OldAccessTokenScopeUserEmail OldAccessTokenScope = "user:email" + OldAccessTokenScopeUserFollow OldAccessTokenScope = "user:follow" + + OldAccessTokenScopeDeleteRepo OldAccessTokenScope = "delete_repo" + + OldAccessTokenScopePackage OldAccessTokenScope = "package" + OldAccessTokenScopeWritePackage OldAccessTokenScope = "write:package" + OldAccessTokenScopeReadPackage OldAccessTokenScope = "read:package" + OldAccessTokenScopeDeletePackage OldAccessTokenScope = "delete:package" + + OldAccessTokenScopeAdminGPGKey OldAccessTokenScope = "admin:gpg_key" + OldAccessTokenScopeWriteGPGKey OldAccessTokenScope = "write:gpg_key" + OldAccessTokenScopeReadGPGKey OldAccessTokenScope = "read:gpg_key" + + OldAccessTokenScopeAdminApplication OldAccessTokenScope = "admin:application" + OldAccessTokenScopeWriteApplication OldAccessTokenScope = "write:application" + OldAccessTokenScopeReadApplication OldAccessTokenScope = "read:application" + + OldAccessTokenScopeSudo OldAccessTokenScope = "sudo" +) + +var accessTokenScopeMap = map[OldAccessTokenScope][]AccessTokenScope{ + OldAccessTokenScopeAll: {AccessTokenScopeAll}, + OldAccessTokenScopeRepo: {AccessTokenScopeWriteRepository}, + OldAccessTokenScopeRepoStatus: {AccessTokenScopeWriteRepository}, + OldAccessTokenScopePublicRepo: {AccessTokenScopePublicOnly, AccessTokenScopeWriteRepository}, + OldAccessTokenScopeAdminOrg: {AccessTokenScopeWriteOrganization}, + OldAccessTokenScopeWriteOrg: {AccessTokenScopeWriteOrganization}, + OldAccessTokenScopeReadOrg: {AccessTokenScopeReadOrganization}, + OldAccessTokenScopeAdminPublicKey: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeWritePublicKey: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeReadPublicKey: {AccessTokenScopeReadUser}, + OldAccessTokenScopeAdminRepoHook: {AccessTokenScopeWriteRepository}, + OldAccessTokenScopeWriteRepoHook: {AccessTokenScopeWriteRepository}, + OldAccessTokenScopeReadRepoHook: {AccessTokenScopeReadRepository}, + OldAccessTokenScopeAdminOrgHook: {AccessTokenScopeWriteOrganization}, + OldAccessTokenScopeNotification: {AccessTokenScopeWriteNotification}, + OldAccessTokenScopeUser: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeReadUser: {AccessTokenScopeReadUser}, + OldAccessTokenScopeUserEmail: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeUserFollow: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeDeleteRepo: {AccessTokenScopeWriteRepository}, + OldAccessTokenScopePackage: {AccessTokenScopeWritePackage}, + OldAccessTokenScopeWritePackage: {AccessTokenScopeWritePackage}, + OldAccessTokenScopeReadPackage: {AccessTokenScopeReadPackage}, + OldAccessTokenScopeDeletePackage: {AccessTokenScopeWritePackage}, + OldAccessTokenScopeAdminGPGKey: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeWriteGPGKey: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeReadGPGKey: {AccessTokenScopeReadUser}, + OldAccessTokenScopeAdminApplication: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeWriteApplication: {AccessTokenScopeWriteUser}, + OldAccessTokenScopeReadApplication: {AccessTokenScopeReadUser}, + OldAccessTokenScopeSudo: {AccessTokenScopeWriteAdmin}, +} + +type AccessToken struct { + ID int64 `xorm:"pk autoincr"` + Scope string +} + +func ConvertScopedAccessTokens(x *xorm.Engine) error { + var tokens []*AccessToken + + if err := x.Find(&tokens); err != nil { + return err + } + + for _, token := range tokens { + var scopes []string + allNewScopesMap := make(map[AccessTokenScope]bool) + for _, oldScope := range strings.Split(token.Scope, ",") { + if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists { + for _, newScope := range newScopes { + allNewScopesMap[newScope] = true + } + } else { + log.Debug("access token scope not recognized as old token scope %s; preserving it", oldScope) + scopes = append(scopes, oldScope) + } + } + + for s := range allNewScopesMap { + scopes = append(scopes, string(s)) + } + scope := AccessTokenScope(strings.Join(scopes, ",")) + + // normalize the scope + normScope := scope.NormalizePreservingUnknown() + + token.Scope = string(normScope) + + // update the db entry with the new scope + if _, err := x.Cols("scope").ID(token.ID).Update(token); err != nil { + return err + } + } + + return nil +} diff --git a/models/migrations/v1_20/v259_test.go b/models/migrations/v1_20/v259_test.go new file mode 100644 index 0000000000..5bc9a71391 --- /dev/null +++ b/models/migrations/v1_20/v259_test.go @@ -0,0 +1,110 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "sort" + "strings" + "testing" + + "code.gitea.io/gitea/models/migrations/base" + + "github.com/stretchr/testify/assert" +) + +type testCase struct { + Old OldAccessTokenScope + New AccessTokenScope +} + +func createOldTokenScope(scopes ...OldAccessTokenScope) OldAccessTokenScope { + s := make([]string, 0, len(scopes)) + for _, os := range scopes { + s = append(s, string(os)) + } + return OldAccessTokenScope(strings.Join(s, ",")) +} + +func createNewTokenScope(scopes ...AccessTokenScope) AccessTokenScope { + s := make([]string, 0, len(scopes)) + for _, os := range scopes { + s = append(s, string(os)) + } + return AccessTokenScope(strings.Join(s, ",")) +} + +func Test_ConvertScopedAccessTokens(t *testing.T) { + tests := []testCase{ + { + createOldTokenScope(OldAccessTokenScopeRepo, OldAccessTokenScopeUserFollow), + createNewTokenScope(AccessTokenScopeWriteRepository, AccessTokenScopeWriteUser), + }, + { + createOldTokenScope(OldAccessTokenScopeUser, OldAccessTokenScopeWritePackage, OldAccessTokenScopeSudo), + createNewTokenScope(AccessTokenScopeWriteAdmin, AccessTokenScopeWritePackage, AccessTokenScopeWriteUser), + }, + { + createOldTokenScope(), + createNewTokenScope(), + }, + { + createOldTokenScope(OldAccessTokenScopeReadGPGKey, OldAccessTokenScopeReadOrg, OldAccessTokenScopeAll), + createNewTokenScope(AccessTokenScopeAll), + }, + { + createOldTokenScope(OldAccessTokenScopeReadGPGKey, "invalid"), + createNewTokenScope("invalid", AccessTokenScopeReadUser), + }, + } + + // add a test for each individual mapping + for oldScope, newScope := range accessTokenScopeMap { + tests = append(tests, testCase{ + oldScope, + createNewTokenScope(newScope...), + }) + } + + x, deferable := base.PrepareTestEnv(t, 0, new(AccessToken)) + defer deferable() + if x == nil || t.Failed() { + t.Skip() + return + } + + // verify that no fixtures were loaded + count, err := x.Count(&AccessToken{}) + assert.NoError(t, err) + assert.Equal(t, int64(0), count) + + for _, tc := range tests { + _, err = x.Insert(&AccessToken{ + Scope: string(tc.Old), + }) + assert.NoError(t, err) + } + + // migrate the scopes + err = ConvertScopedAccessTokens(x) + assert.NoError(t, err) + + // migrate the scopes again (migration should be idempotent) + err = ConvertScopedAccessTokens(x) + assert.NoError(t, err) + + tokens := make([]AccessToken, 0) + err = x.Find(&tokens) + assert.NoError(t, err) + assert.Equal(t, len(tests), len(tokens)) + + // sort the tokens (insertion order by auto-incrementing primary key) + sort.Slice(tokens, func(i, j int) bool { + return tokens[i].ID < tokens[j].ID + }) + + // verify that the converted scopes are equal to the expected test result + for idx, newToken := range tokens { + assert.Equal(t, string(tests[idx].New), newToken.Scope) + } +} diff --git a/modules/context/permission.go b/modules/context/permission.go index cc53fb99ed..0f72b8e244 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -111,28 +111,36 @@ func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) { } } -// RequireRepoScopedToken check whether personal access token has repo scope -func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository) { +// CheckRepoScopedToken check whether personal access token has repo scope +func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) { if !ctx.IsBasicAuth || ctx.Data["IsApiToken"] != true { return } - var err error scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) if ok { // it's a personal access token but not oauth2 token var scopeMatched bool - scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeRepo) + + requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository) + + // check if scope only applies to public resources + publicOnly, err := scope.PublicOnly() if err != nil { ctx.ServerError("HasScope", err) return } - if !scopeMatched && !repo.IsPrivate { - scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopePublicRepo) - if err != nil { - ctx.ServerError("HasScope", err) - return - } + + if publicOnly && repo.IsPrivate { + ctx.Error(http.StatusForbidden) + return } + + scopeMatched, err = scope.HasScope(requiredScopes...) + if err != nil { + ctx.ServerError("HasScope", err) + return + } + if !scopeMatched { ctx.Error(http.StatusForbidden) return diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 517d829c84..2746e53023 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -796,7 +796,6 @@ unbind_success = The social account has been unlinked from your Gitea account. manage_access_token = Manage Access Tokens generate_new_token = Generate New Token tokens_desc = These tokens grant access to your account using the Gitea API. -new_token_desc = Applications using a token have full access to your account. token_name = Token Name generate_token = Generate Token generate_token_success = Your new token has been generated. Copy it now as it will not be shown again. @@ -807,8 +806,13 @@ access_token_deletion_cancel_action = Cancel access_token_deletion_confirm_action = Delete access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue? delete_token_success = The token has been deleted. Applications using it no longer have access to your account. -select_scopes = Select scopes -scopes_list = Scopes: +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 +scoped_token_desc = Selected token scopes limit authentication only to the corresponding <a %s>API</a> routes. Read the <a %s>documentation</a> for more information. +at_least_one_permission = You must select at least one permission to create a token +permissions_list = Permissions: manage_oauth2_applications = Manage OAuth2 Applications edit_oauth2_application = Edit OAuth2 Application diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 45e36e84fe..37361a8b96 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -236,44 +236,85 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) } } +// if a token is being used for auth, we check that it contains the required scope +// if a token is not being used, reqToken will enforce other sign in methods +func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) { + return func(ctx *context.APIContext) { + // no scope required + if len(requiredScopeCategories) == 0 { + return + } + + // Need OAuth2 token to be present. + scope, scopeExists := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) + if ctx.Data["IsApiToken"] != true || !scopeExists { + return + } + + ctx.Data["ApiTokenScopePublicRepoOnly"] = false + ctx.Data["ApiTokenScopePublicOrgOnly"] = false + + // use the http method to determine the access level + requiredScopeLevel := auth_model.Read + if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" { + requiredScopeLevel = auth_model.Write + } + + // get the required scope for the given access level and category + requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...) + + // check if scope only applies to public resources + publicOnly, err := scope.PublicOnly() + if err != nil { + ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error()) + return + } + + // this context is used by the middleware in the specific route + ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository) + ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization) + + allow, err := scope.HasScope(requiredScopes...) + if err != nil { + ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error()) + return + } + + if allow { + return + } + + ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes)) + } +} + // Contexter middleware already checks token for user sign in process. -func reqToken(requiredScope auth_model.AccessTokenScope) func(ctx *context.APIContext) { +func reqToken() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { // If actions token is present if true == ctx.Data["IsActionsToken"] { return } - // If OAuth2 token is present - if _, ok := ctx.Data["ApiTokenScope"]; ctx.Data["IsApiToken"] == true && ok { - // no scope required - if requiredScope == "" { - return - } + if true == ctx.Data["IsApiToken"] { + publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"] + publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"] - // check scope - scope := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) - allow, err := scope.HasScope(requiredScope) - if err != nil { - ctx.Error(http.StatusForbidden, "reqToken", "parsing token failed: "+err.Error()) - return - } - if allow { + if pubRepoExists && publicRepo.(bool) && + ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate { + ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos") return } - // if requires 'repo' scope, but only has 'public_repo' scope, allow it only if the repo is public - if requiredScope == auth_model.AccessTokenScopeRepo { - if allowPublicRepo, err := scope.HasScope(auth_model.AccessTokenScopePublicRepo); err == nil && allowPublicRepo { - if ctx.Repo.Repository != nil && !ctx.Repo.Repository.IsPrivate { - return - } - } + if pubOrgExists && publicOrg.(bool) && + ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic { + ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs") + return } - ctx.Error(http.StatusForbidden, "reqToken", "token does not have required scope: "+requiredScope) return } + if ctx.IsBasicAuth { ctx.CheckForOTP() return @@ -700,7 +741,7 @@ func Routes(ctx gocontext.Context) *web.Route { ctx.Redirect(setting.AppSubURL + "/api/swagger") }) } - m.Get("/version", misc.Version) + if setting.Federation.Enabled { m.Get("/nodeinfo", misc.NodeInfo) m.Group("/activitypub", func() { @@ -713,37 +754,43 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("", activitypub.Person) m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) }, context_service.UserIDAssignmentAPI()) - }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub)) } - m.Get("/signing-key.gpg", misc.SigningKey) - m.Post("/markup", bind(api.MarkupOption{}), misc.Markup) - m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) - m.Post("/markdown/raw", misc.MarkdownRaw) - m.Get("/gitignore/templates", misc.ListGitignoresTemplates) - m.Get("/gitignore/templates/{name}", misc.GetGitignoreTemplateInfo) - m.Get("/licenses", misc.ListLicenseTemplates) - m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo) - m.Get("/label/templates", misc.ListLabelTemplates) - m.Get("/label/templates/{name}", misc.GetLabelTemplate) - m.Group("/settings", func() { - m.Get("/ui", settings.GetGeneralUISettings) - m.Get("/api", settings.GetGeneralAPISettings) - m.Get("/attachment", settings.GetGeneralAttachmentSettings) - m.Get("/repository", settings.GetGeneralRepoSettings) - }) - // Notifications (requires 'notification' scope) + // Misc (requires 'misc' scope) + m.Group("", func() { + m.Get("/version", misc.Version) + m.Get("/signing-key.gpg", misc.SigningKey) + m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup) + m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown) + m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw) + m.Get("/gitignore/templates", misc.ListGitignoresTemplates) + m.Get("/gitignore/templates/{name}", misc.GetGitignoreTemplateInfo) + m.Get("/licenses", misc.ListLicenseTemplates) + m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo) + m.Get("/label/templates", misc.ListLabelTemplates) + m.Get("/label/templates/{name}", misc.GetLabelTemplate) + + m.Group("/settings", func() { + m.Get("/ui", settings.GetGeneralUISettings) + m.Get("/api", settings.GetGeneralAPISettings) + m.Get("/attachment", settings.GetGeneralAttachmentSettings) + m.Get("/repository", settings.GetGeneralRepoSettings) + }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryMisc)) + + // Notifications (requires 'notifications' scope) m.Group("/notifications", func() { m.Combo(""). Get(notify.ListNotifications). - Put(notify.ReadNotifications) + Put(notify.ReadNotifications, reqToken()) m.Get("/new", notify.NewAvailable) m.Combo("/threads/{id}"). Get(notify.GetThread). - Patch(notify.ReadThread) - }, reqToken(auth_model.AccessTokenScopeNotification)) + Patch(notify.ReadThread, reqToken()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) - // Users (no scope required) + // Users (requires user scope) m.Group("/users", func() { m.Get("/search", reqExploreSignIn(), user.Search) @@ -754,18 +801,18 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/heatmap", user.GetUserHeatmapData) } - m.Get("/repos", reqExploreSignIn(), user.ListUserRepos) + m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqExploreSignIn(), user.ListUserRepos) m.Group("/tokens", func() { m.Combo("").Get(user.ListAccessTokens). - Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) - m.Combo("/{id}").Delete(user.DeleteAccessToken) + Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken) + m.Combo("/{id}").Delete(reqToken(), user.DeleteAccessToken) }, reqBasicAuth()) m.Get("/activities/feeds", user.ListUserActivityFeeds) }, context_service.UserAssignmentAPI()) - }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) - // (no scope required) + // Users (requires user scope) m.Group("/users", func() { m.Group("/{username}", func() { m.Get("/keys", user.ListPublicKeys) @@ -781,59 +828,61 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/subscriptions", user.GetWatchedRepos) }, context_service.UserAssignmentAPI()) - }, reqToken("")) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) + // Users (requires user scope) m.Group("/user", func() { m.Get("", user.GetAuthenticatedUser) m.Group("/settings", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadUser), user.GetUserSettings) - m.Patch("", reqToken(auth_model.AccessTokenScopeUser), bind(api.UserSettingsOptions{}), user.UpdateUserSettings) - }) - m.Combo("/emails").Get(reqToken(auth_model.AccessTokenScopeReadUser), user.ListEmails). - Post(reqToken(auth_model.AccessTokenScopeUser), bind(api.CreateEmailOption{}), user.AddEmail). - Delete(reqToken(auth_model.AccessTokenScopeUser), bind(api.DeleteEmailOption{}), user.DeleteEmail) + m.Get("", user.GetUserSettings) + m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings) + }, reqToken()) + m.Combo("/emails"). + Get(user.ListEmails). + Post(bind(api.CreateEmailOption{}), user.AddEmail). + Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail) m.Get("/followers", user.ListMyFollowers) m.Group("/following", func() { m.Get("", user.ListMyFollowing) m.Group("/{username}", func() { m.Get("", user.CheckMyFollowing) - m.Put("", reqToken(auth_model.AccessTokenScopeUserFollow), user.Follow) // requires 'user:follow' scope - m.Delete("", reqToken(auth_model.AccessTokenScopeUserFollow), user.Unfollow) // requires 'user:follow' scope + m.Put("", user.Follow) + m.Delete("", user.Unfollow) }, context_service.UserAssignmentAPI()) }) // (admin:public_key scope) m.Group("/keys", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadPublicKey), user.ListMyPublicKeys). - Post(reqToken(auth_model.AccessTokenScopeWritePublicKey), bind(api.CreateKeyOption{}), user.CreatePublicKey) - m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadPublicKey), user.GetPublicKey). - Delete(reqToken(auth_model.AccessTokenScopeWritePublicKey), user.DeletePublicKey) + m.Combo("").Get(user.ListMyPublicKeys). + Post(bind(api.CreateKeyOption{}), user.CreatePublicKey) + m.Combo("/{id}").Get(user.GetPublicKey). + Delete(user.DeletePublicKey) }) // (admin:application scope) m.Group("/applications", func() { m.Combo("/oauth2"). - Get(reqToken(auth_model.AccessTokenScopeReadApplication), user.ListOauth2Applications). - Post(reqToken(auth_model.AccessTokenScopeWriteApplication), bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application) + Get(user.ListOauth2Applications). + Post(bind(api.CreateOAuth2ApplicationOptions{}), user.CreateOauth2Application) m.Combo("/oauth2/{id}"). - Delete(reqToken(auth_model.AccessTokenScopeWriteApplication), user.DeleteOauth2Application). - Patch(reqToken(auth_model.AccessTokenScopeWriteApplication), bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application). - Get(reqToken(auth_model.AccessTokenScopeReadApplication), user.GetOauth2Application) + Delete(user.DeleteOauth2Application). + Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application). + Get(user.GetOauth2Application) }) // (admin:gpg_key scope) m.Group("/gpg_keys", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadGPGKey), user.ListMyGPGKeys). - Post(reqToken(auth_model.AccessTokenScopeWriteGPGKey), bind(api.CreateGPGKeyOption{}), user.CreateGPGKey) - m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadGPGKey), user.GetGPGKey). - Delete(reqToken(auth_model.AccessTokenScopeWriteGPGKey), user.DeleteGPGKey) + m.Combo("").Get(user.ListMyGPGKeys). + Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey) + m.Combo("/{id}").Get(user.GetGPGKey). + Delete(user.DeleteGPGKey) }) - m.Get("/gpg_key_token", reqToken(auth_model.AccessTokenScopeReadGPGKey), user.GetVerificationToken) - m.Post("/gpg_key_verify", reqToken(auth_model.AccessTokenScopeReadGPGKey), bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey) + m.Get("/gpg_key_token", user.GetVerificationToken) + m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey) // (repo scope) - m.Combo("/repos", reqToken(auth_model.AccessTokenScopeRepo)).Get(user.ListMyRepos). + m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos). Post(bind(api.CreateRepoOption{}), repo.Create) // (repo scope) @@ -844,64 +893,65 @@ func Routes(ctx gocontext.Context) *web.Route { m.Put("", user.Star) m.Delete("", user.Unstar) }, repoAssignment()) - }, reqToken(auth_model.AccessTokenScopeRepo)) - m.Get("/times", reqToken(auth_model.AccessTokenScopeRepo), repo.ListMyTrackedTimes) - m.Get("/stopwatches", reqToken(auth_model.AccessTokenScopeRepo), repo.GetStopwatches) - m.Get("/subscriptions", reqToken(auth_model.AccessTokenScopeRepo), user.GetMyWatchedRepos) - m.Get("/teams", reqToken(auth_model.AccessTokenScopeRepo), org.ListUserTeams) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) + m.Get("/times", repo.ListMyTrackedTimes) + m.Get("/stopwatches", repo.GetStopwatches) + m.Get("/subscriptions", user.GetMyWatchedRepos) + m.Get("/teams", org.ListUserTeams) m.Group("/hooks", func() { m.Combo("").Get(user.ListHooks). Post(bind(api.CreateHookOption{}), user.CreateHook) m.Combo("/{id}").Get(user.GetHook). Patch(bind(api.EditHookOption{}), user.EditHook). Delete(user.DeleteHook) - }, reqToken(auth_model.AccessTokenScopeAdminUserHook), reqWebhooksEnabled()) - }, reqToken("")) + }, reqWebhooksEnabled()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) - // Repositories - m.Post("/org/{org}/repos", reqToken(auth_model.AccessTokenScopeAdminOrg), bind(api.CreateRepoOption{}), repo.CreateOrgRepoDeprecated) + // Repositories (requires repo scope, org scope) + m.Post("/org/{org}/repos", + tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), + reqToken(), + bind(api.CreateRepoOption{}), + repo.CreateOrgRepoDeprecated) - m.Combo("/repositories/{id}", reqToken(auth_model.AccessTokenScopeRepo)).Get(repo.GetByID) + // requires repo scope + m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID) + // Repos (requires repo scope) m.Group("/repos", func() { m.Get("/search", repo.Search) - m.Get("/issues/search", repo.SearchIssues) - // (repo scope) - m.Post("/migrate", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MigrateRepoOptions{}), repo.Migrate) + m.Post("/migrate", reqToken(), bind(api.MigrateRepoOptions{}), repo.Migrate) m.Group("/{username}/{reponame}", func() { m.Combo("").Get(reqAnyRepoReader(), repo.Get). - Delete(reqToken(auth_model.AccessTokenScopeDeleteRepo), reqOwner(), repo.Delete). - Patch(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) - m.Post("/generate", reqToken(auth_model.AccessTokenScopeRepo), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate) + Delete(reqToken(), reqOwner(), repo.Delete). + Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) + m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate) m.Group("/transfer", func() { m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer) m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) - }, reqToken(auth_model.AccessTokenScopeRepo)) - m.Combo("/notifications", reqToken(auth_model.AccessTokenScopeNotification)). - Get(notify.ListRepoNotifications). - Put(notify.ReadRepoNotifications) + }, reqToken()) m.Group("/hooks/git", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.ListGitHooks) + m.Combo("").Get(repo.ListGitHooks) m.Group("/{id}", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.GetGitHook). - Patch(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.EditGitHookOption{}), repo.EditGitHook). - Delete(reqToken(auth_model.AccessTokenScopeWriteRepoHook), repo.DeleteGitHook) + m.Combo("").Get(repo.GetGitHook). + Patch(bind(api.EditGitHookOption{}), repo.EditGitHook). + Delete(repo.DeleteGitHook) }) - }, reqAdmin(), reqGitHook(), context.ReferencesGitRepo(true)) + }, reqToken(), reqAdmin(), reqGitHook(), context.ReferencesGitRepo(true)) m.Group("/hooks", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.ListHooks). - Post(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.CreateHookOption{}), repo.CreateHook) + m.Combo("").Get(repo.ListHooks). + Post(bind(api.CreateHookOption{}), repo.CreateHook) m.Group("/{id}", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadRepoHook), repo.GetHook). - Patch(reqToken(auth_model.AccessTokenScopeWriteRepoHook), bind(api.EditHookOption{}), repo.EditHook). - Delete(reqToken(auth_model.AccessTokenScopeWriteRepoHook), repo.DeleteHook) - m.Post("/tests", reqToken(auth_model.AccessTokenScopeReadRepoHook), context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook) + m.Combo("").Get(repo.GetHook). + Patch(bind(api.EditHookOption{}), repo.EditHook). + Delete(repo.DeleteHook) + m.Post("/tests", context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook) }) - }, reqAdmin(), reqWebhooksEnabled()) + }, reqToken(), reqAdmin(), reqWebhooksEnabled()) m.Group("/collaborators", func() { m.Get("", reqAnyRepoReader(), repo.ListCollaborators) m.Group("/{collaborator}", func() { @@ -910,25 +960,25 @@ func Routes(ctx gocontext.Context) *web.Route { Delete(reqAdmin(), repo.DeleteCollaborator) m.Get("/permission", repo.GetRepoPermissions) }) - }, reqToken(auth_model.AccessTokenScopeRepo)) - m.Get("/assignees", reqToken(auth_model.AccessTokenScopeRepo), reqAnyRepoReader(), repo.GetAssignees) - m.Get("/reviewers", reqToken(auth_model.AccessTokenScopeRepo), reqAnyRepoReader(), repo.GetReviewers) + }, reqToken()) + m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees) + m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers) m.Group("/teams", func() { m.Get("", reqAnyRepoReader(), repo.ListTeams) m.Combo("/{team}").Get(reqAnyRepoReader(), repo.IsTeam). Put(reqAdmin(), repo.AddTeam). Delete(reqAdmin(), repo.DeleteTeam) - }, reqToken(auth_model.AccessTokenScopeRepo)) + }, reqToken()) m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile) m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS) m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive) m.Combo("/forks").Get(repo.ListForks). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork) + Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork) m.Group("/branches", func() { m.Get("", repo.ListBranches) m.Get("/*", repo.GetBranch) - m.Delete("/*", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), repo.DeleteBranch) - m.Post("", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch) + m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), repo.DeleteBranch) + m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateBranchRepoOption{}), repo.CreateBranch) }, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode)) m.Group("/branch_protections", func() { m.Get("", repo.ListBranchProtections) @@ -938,218 +988,112 @@ func Routes(ctx gocontext.Context) *web.Route { m.Patch("", bind(api.EditBranchProtectionOption{}), repo.EditBranchProtection) m.Delete("", repo.DeleteBranchProtection) }) - }, reqToken(auth_model.AccessTokenScopeRepo), reqAdmin()) + }, reqToken(), reqAdmin()) m.Group("/tags", func() { m.Get("", repo.ListTags) m.Get("/*", repo.GetTag) - m.Post("", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), bind(api.CreateTagOption{}), repo.CreateTag) - m.Delete("/*", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteTag) + m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateTagOption{}), repo.CreateTag) + m.Delete("/*", reqToken(), repo.DeleteTag) }, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true)) m.Group("/keys", func() { m.Combo("").Get(repo.ListDeployKeys). Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) m.Combo("/{id}").Get(repo.GetDeployKey). Delete(repo.DeleteDeploykey) - }, reqToken(auth_model.AccessTokenScopeRepo), reqAdmin()) + }, reqToken(), reqAdmin()) m.Group("/times", func() { m.Combo("").Get(repo.ListTrackedTimesByRepository) m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser) - }, mustEnableIssues, reqToken(auth_model.AccessTokenScopeRepo)) + }, mustEnableIssues, reqToken()) m.Group("/wiki", func() { m.Combo("/page/{pageName}"). Get(repo.GetWikiPage). - Patch(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage). - Delete(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage) + Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage). + Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage) m.Get("/revisions/{pageName}", repo.ListPageRevisions) - m.Post("/new", mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage) + m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage) m.Get("/pages", repo.ListWikiPages) }, mustEnableWiki) - m.Group("/issues", func() { - m.Combo("").Get(repo.ListIssues). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue) - m.Get("/pinned", repo.ListPinnedIssues) - m.Group("/comments", func() { - m.Get("", repo.ListRepoIssueComments) - m.Group("/{id}", func() { - m.Combo(""). - Get(repo.GetIssueComment). - Patch(mustNotBeArchived, reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditIssueCommentOption{}), repo.EditIssueComment). - Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueComment) - m.Combo("/reactions"). - Get(repo.GetIssueCommentReactions). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction). - Delete(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction) - m.Group("/assets", func() { - m.Combo(""). - Get(repo.ListIssueCommentAttachments). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CreateIssueCommentAttachment) - m.Combo("/{asset}"). - Get(repo.GetIssueCommentAttachment). - Patch(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment). - Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.DeleteIssueCommentAttachment) - }, mustEnableAttachments) - }) - }) - m.Group("/{index}", func() { - m.Combo("").Get(repo.GetIssue). - Patch(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditIssueOption{}), repo.EditIssue). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue) - m.Group("/comments", func() { - m.Combo("").Get(repo.ListIssueComments). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) - m.Combo("/{id}", reqToken(auth_model.AccessTokenScopeRepo)).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated). - Delete(repo.DeleteIssueCommentDeprecated) - }) - m.Get("/timeline", repo.ListIssueCommentsAndTimeline) - m.Group("/labels", func() { - m.Combo("").Get(repo.ListIssueLabels). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueLabelsOption{}), repo.AddIssueLabels). - Put(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). - Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.ClearIssueLabels) - m.Delete("/{id}", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueLabel) - }) - m.Group("/times", func() { - m.Combo(""). - Get(repo.ListTrackedTimes). - Post(bind(api.AddTimeOption{}), repo.AddTime). - Delete(repo.ResetIssueTime) - m.Delete("/{id}", repo.DeleteTime) - }, reqToken(auth_model.AccessTokenScopeRepo)) - m.Combo("/deadline").Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline) - m.Group("/stopwatch", func() { - m.Post("/start", reqToken(auth_model.AccessTokenScopeRepo), repo.StartIssueStopwatch) - m.Post("/stop", reqToken(auth_model.AccessTokenScopeRepo), repo.StopIssueStopwatch) - m.Delete("/delete", reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteIssueStopwatch) - }) - m.Group("/subscriptions", func() { - m.Get("", repo.GetIssueSubscribers) - m.Get("/check", reqToken(auth_model.AccessTokenScopeRepo), repo.CheckIssueSubscription) - m.Put("/{user}", reqToken(auth_model.AccessTokenScopeRepo), repo.AddIssueSubscription) - m.Delete("/{user}", reqToken(auth_model.AccessTokenScopeRepo), repo.DelIssueSubscription) - }) - m.Combo("/reactions"). - Get(repo.GetIssueReactions). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.PostIssueReaction). - Delete(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditReactionOption{}), repo.DeleteIssueReaction) - m.Group("/assets", func() { - m.Combo(""). - Get(repo.ListIssueAttachments). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CreateIssueAttachment) - m.Combo("/{asset}"). - Get(repo.GetIssueAttachment). - Patch(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment). - Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.DeleteIssueAttachment) - }, mustEnableAttachments) - m.Combo("/dependencies"). - Get(repo.GetIssueDependencies). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.IssueMeta{}), repo.CreateIssueDependency). - Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.IssueMeta{}), repo.RemoveIssueDependency) - m.Combo("/blocks"). - Get(repo.GetIssueBlocks). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueMeta{}), repo.CreateIssueBlocking). - Delete(reqToken(auth_model.AccessTokenScopeRepo), bind(api.IssueMeta{}), repo.RemoveIssueBlocking) - m.Group("/pin", func() { - m.Combo(""). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), repo.PinIssue). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), repo.UnpinIssue) - m.Patch("/{position}", reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), repo.MoveIssuePin) - }) - }) - }, mustEnableIssuesOrPulls) - m.Group("/labels", func() { - m.Combo("").Get(repo.ListLabels). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel) - m.Combo("/{id}").Get(repo.GetLabel). - Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel) - }) - m.Post("/markup", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkupOption{}), misc.Markup) - m.Post("/markdown", reqToken(auth_model.AccessTokenScopeRepo), bind(api.MarkdownOption{}), misc.Markdown) - m.Post("/markdown/raw", reqToken(auth_model.AccessTokenScopeRepo), misc.MarkdownRaw) - m.Group("/milestones", func() { - m.Combo("").Get(repo.ListMilestones). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) - m.Combo("/{id}").Get(repo.GetMilestone). - Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone) - }) + m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup) + m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown) + m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw) m.Get("/stargazers", repo.ListStargazers) m.Get("/subscribers", repo.ListSubscribers) m.Group("/subscription", func() { m.Get("", user.IsWatching) - m.Put("", reqToken(auth_model.AccessTokenScopeRepo), user.Watch) - m.Delete("", reqToken(auth_model.AccessTokenScopeRepo), user.Unwatch) + m.Put("", reqToken(), user.Watch) + m.Delete("", reqToken(), user.Unwatch) }) m.Group("/releases", func() { m.Combo("").Get(repo.ListReleases). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) + Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) m.Combo("/latest").Get(repo.GetLatestRelease) m.Group("/{id}", func() { m.Combo("").Get(repo.GetRelease). - Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease) + Patch(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease). + Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteRelease) m.Group("/assets", func() { m.Combo("").Get(repo.ListReleaseAttachments). - Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment) + Post(reqToken(), reqRepoWriter(unit.TypeReleases), repo.CreateReleaseAttachment) m.Combo("/{asset}").Get(repo.GetReleaseAttachment). - Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment) + Patch(reqToken(), reqRepoWriter(unit.TypeReleases), bind(api.EditAttachmentOptions{}), repo.EditReleaseAttachment). + Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseAttachment) }) }) m.Group("/tags", func() { m.Combo("/{tag}"). Get(repo.GetReleaseByTag). - Delete(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag) + Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag) }) }, reqRepoReader(unit.TypeReleases)) - m.Post("/mirror-sync", reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeCode), repo.MirrorSync) - m.Post("/push_mirrors-sync", reqAdmin(), reqToken(auth_model.AccessTokenScopeRepo), repo.PushMirrorSync) + m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), repo.MirrorSync) + m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), repo.PushMirrorSync) m.Group("/push_mirrors", func() { m.Combo("").Get(repo.ListPushMirrors). Post(bind(api.CreatePushMirrorOption{}), repo.AddPushMirror) m.Combo("/{name}"). Delete(repo.DeletePushMirrorByRemoteName). Get(repo.GetPushMirrorByName) - }, reqAdmin(), reqToken(auth_model.AccessTokenScopeRepo)) + }, reqAdmin(), reqToken()) m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig) m.Group("/pulls", func() { m.Combo("").Get(repo.ListPullRequests). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) + Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest) m.Get("/pinned", repo.ListPinnedPullRequests) m.Group("/{index}", func() { m.Combo("").Get(repo.GetPullRequest). - Patch(reqToken(auth_model.AccessTokenScopeRepo), bind(api.EditPullRequestOption{}), repo.EditPullRequest) + Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest) m.Get(".{diffType:diff|patch}", repo.DownloadPullDiffOrPatch) - m.Post("/update", reqToken(auth_model.AccessTokenScopeRepo), repo.UpdatePullRequest) + m.Post("/update", reqToken(), repo.UpdatePullRequest) m.Get("/commits", repo.GetPullRequestCommits) m.Get("/files", repo.GetPullRequestFiles) m.Combo("/merge").Get(repo.IsPullRequestMerged). - Post(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest). - Delete(reqToken(auth_model.AccessTokenScopeRepo), mustNotBeArchived, repo.CancelScheduledAutoMerge) + Post(reqToken(), mustNotBeArchived, bind(forms.MergePullRequestForm{}), repo.MergePullRequest). + Delete(reqToken(), mustNotBeArchived, repo.CancelScheduledAutoMerge) m.Group("/reviews", func() { m.Combo(""). Get(repo.ListPullReviews). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview) + Post(reqToken(), bind(api.CreatePullReviewOptions{}), repo.CreatePullReview) m.Group("/{id}", func() { m.Combo(""). Get(repo.GetPullReview). - Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeletePullReview). - Post(reqToken(auth_model.AccessTokenScopeRepo), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview) + Delete(reqToken(), repo.DeletePullReview). + Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview) m.Combo("/comments"). Get(repo.GetPullReviewComments) - m.Post("/dismissals", reqToken(auth_model.AccessTokenScopeRepo), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview) - m.Post("/undismissals", reqToken(auth_model.AccessTokenScopeRepo), repo.UnDismissPullReview) + m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview) + m.Post("/undismissals", reqToken(), repo.UnDismissPullReview) }) }) - m.Combo("/requested_reviewers", reqToken(auth_model.AccessTokenScopeRepo)). + m.Combo("/requested_reviewers", reqToken()). Delete(bind(api.PullReviewRequestOptions{}), repo.DeleteReviewRequests). Post(bind(api.PullReviewRequestOptions{}), repo.CreateReviewRequests) }) }, mustAllowPulls, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo()) m.Group("/statuses", func() { m.Combo("/{sha}").Get(repo.GetCommitStatuses). - Post(reqToken(auth_model.AccessTokenScopeRepoStatus), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) + Post(reqToken(), reqRepoWriter(unit.TypeCode), bind(api.CreateStatusOption{}), repo.NewCommitStatus) }, reqRepoReader(unit.TypeCode)) m.Group("/commits", func() { m.Get("", context.ReferencesGitRepo(), repo.GetAllCommits) @@ -1170,24 +1114,24 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/tags/{sha}", repo.GetAnnotatedTag) m.Get("/notes/{sha}", repo.GetNote) }, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode)) - m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(auth_model.AccessTokenScopeRepo), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch) + m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), repo.ApplyDiffPatch) m.Group("/contents", func() { m.Get("", repo.GetContentsList) - m.Post("", reqToken(auth_model.AccessTokenScopeRepo), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, repo.ChangeFiles) + m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, repo.ChangeFiles) m.Get("/*", repo.GetContents) m.Group("/*", func() { m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile) m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, repo.UpdateFile) m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, repo.DeleteFile) - }, reqToken(auth_model.AccessTokenScopeRepo)) + }, reqToken()) }, reqRepoReader(unit.TypeCode)) m.Get("/signing-key.gpg", misc.SigningKey) m.Group("/topics", func() { m.Combo("").Get(repo.ListTopics). - Put(reqToken(auth_model.AccessTokenScopeRepo), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics) + Put(reqToken(), reqAdmin(), bind(api.RepoTopicOptions{}), repo.UpdateTopics) m.Group("/{topic}", func() { - m.Combo("").Put(reqToken(auth_model.AccessTokenScopeRepo), repo.AddTopic). - Delete(reqToken(auth_model.AccessTokenScopeRepo), repo.DeleteTopic) + m.Combo("").Put(reqToken(), repo.AddTopic). + Delete(reqToken(), repo.DeleteTopic) }, reqAdmin()) }, reqAnyRepoReader()) m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates) @@ -1197,54 +1141,177 @@ func Routes(ctx gocontext.Context) *web.Route { m.Get("/activities/feeds", repo.ListRepoActivityFeeds) m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed) }, repoAssignment()) - }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) + + // Notifications (requires notifications scope) + m.Group("/repos", func() { + m.Group("/{username}/{reponame}", func() { + m.Combo("/notifications", reqToken()). + Get(notify.ListRepoNotifications). + Put(notify.ReadRepoNotifications) + }, repoAssignment()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) + + // Issue (requires issue scope) + m.Group("/repos", func() { + m.Get("/issues/search", repo.SearchIssues) + + m.Group("/{username}/{reponame}", func() { + m.Group("/issues", func() { + m.Combo("").Get(repo.ListIssues). + Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue) + m.Get("/pinned", repo.ListPinnedIssues) + m.Group("/comments", func() { + m.Get("", repo.ListRepoIssueComments) + m.Group("/{id}", func() { + m.Combo(""). + Get(repo.GetIssueComment). + Patch(mustNotBeArchived, reqToken(), bind(api.EditIssueCommentOption{}), repo.EditIssueComment). + Delete(reqToken(), repo.DeleteIssueComment) + m.Combo("/reactions"). + Get(repo.GetIssueCommentReactions). + Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction). + Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction) + m.Group("/assets", func() { + m.Combo(""). + Get(repo.ListIssueCommentAttachments). + Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment) + m.Combo("/{asset}"). + Get(repo.GetIssueCommentAttachment). + Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment). + Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment) + }, mustEnableAttachments) + }) + }) + m.Group("/{index}", func() { + m.Combo("").Get(repo.GetIssue). + Patch(reqToken(), bind(api.EditIssueOption{}), repo.EditIssue). + Delete(reqToken(), reqAdmin(), context.ReferencesGitRepo(), repo.DeleteIssue) + m.Group("/comments", func() { + m.Combo("").Get(repo.ListIssueComments). + Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment) + m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated). + Delete(repo.DeleteIssueCommentDeprecated) + }) + m.Get("/timeline", repo.ListIssueCommentsAndTimeline) + m.Group("/labels", func() { + m.Combo("").Get(repo.ListIssueLabels). + Post(reqToken(), bind(api.IssueLabelsOption{}), repo.AddIssueLabels). + Put(reqToken(), bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels). + Delete(reqToken(), repo.ClearIssueLabels) + m.Delete("/{id}", reqToken(), repo.DeleteIssueLabel) + }) + m.Group("/times", func() { + m.Combo(""). + Get(repo.ListTrackedTimes). + Post(bind(api.AddTimeOption{}), repo.AddTime). + Delete(repo.ResetIssueTime) + m.Delete("/{id}", repo.DeleteTime) + }, reqToken()) + m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline) + m.Group("/stopwatch", func() { + m.Post("/start", repo.StartIssueStopwatch) + m.Post("/stop", repo.StopIssueStopwatch) + m.Delete("/delete", repo.DeleteIssueStopwatch) + }, reqToken()) + m.Group("/subscriptions", func() { + m.Get("", repo.GetIssueSubscribers) + m.Get("/check", reqToken(), repo.CheckIssueSubscription) + m.Put("/{user}", reqToken(), repo.AddIssueSubscription) + m.Delete("/{user}", reqToken(), repo.DelIssueSubscription) + }) + m.Combo("/reactions"). + Get(repo.GetIssueReactions). + Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction). + Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction) + m.Group("/assets", func() { + m.Combo(""). + Get(repo.ListIssueAttachments). + Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment) + m.Combo("/{asset}"). + Get(repo.GetIssueAttachment). + Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment). + Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment) + }, mustEnableAttachments) + m.Combo("/dependencies"). + Get(repo.GetIssueDependencies). + Post(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.CreateIssueDependency). + Delete(reqToken(), mustNotBeArchived, bind(api.IssueMeta{}), repo.RemoveIssueDependency) + m.Combo("/blocks"). + Get(repo.GetIssueBlocks). + Post(reqToken(), bind(api.IssueMeta{}), repo.CreateIssueBlocking). + Delete(reqToken(), bind(api.IssueMeta{}), repo.RemoveIssueBlocking) + m.Group("/pin", func() { + m.Combo(""). + Post(reqToken(), reqAdmin(), repo.PinIssue). + Delete(reqToken(), reqAdmin(), repo.UnpinIssue) + m.Patch("/{position}", reqToken(), reqAdmin(), repo.MoveIssuePin) + }) + }) + }, mustEnableIssuesOrPulls) + m.Group("/labels", func() { + m.Combo("").Get(repo.ListLabels). + Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateLabelOption{}), repo.CreateLabel) + m.Combo("/{id}").Get(repo.GetLabel). + Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditLabelOption{}), repo.EditLabel). + Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteLabel) + }) + m.Group("/milestones", func() { + m.Combo("").Get(repo.ListMilestones). + Post(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.CreateMilestoneOption{}), repo.CreateMilestone) + m.Combo("/{id}").Get(repo.GetMilestone). + Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). + Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone) + }) + }, repoAssignment()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)) // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs m.Group("/packages/{username}", func() { m.Group("/{type}/{name}/{version}", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadPackage), packages.GetPackage) - m.Delete("", reqToken(auth_model.AccessTokenScopeDeletePackage), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) - m.Get("/files", reqToken(auth_model.AccessTokenScopeReadPackage), packages.ListPackageFiles) + m.Get("", reqToken(), packages.GetPackage) + m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) + m.Get("/files", reqToken(), packages.ListPackageFiles) }) - m.Get("/", reqToken(auth_model.AccessTokenScopeReadPackage), packages.ListPackages) - }, context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead)) + m.Get("/", reqToken(), packages.ListPackages) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead)) // Organizations - m.Get("/user/orgs", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListMyOrgs) + m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) m.Group("/users/{username}/orgs", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListUserOrgs) - m.Get("/{org}/permissions", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetUserOrgsPermissions) - }, context_service.UserAssignmentAPI()) - m.Post("/orgs", reqToken(auth_model.AccessTokenScopeWriteOrg), bind(api.CreateOrgOption{}), org.Create) - m.Get("/orgs", org.GetAll) + m.Get("", reqToken(), org.ListUserOrgs) + m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI()) + m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create) + m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization)) m.Group("/orgs/{org}", func() { m.Combo("").Get(org.Get). - Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.Delete) + Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit). + Delete(reqToken(), reqOrgOwnership(), org.Delete) m.Combo("/repos").Get(user.ListOrgRepos). - Post(reqToken(auth_model.AccessTokenScopeWriteOrg), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) + Post(reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo) m.Group("/members", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListMembers) - m.Combo("/{username}").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.IsMember). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteMember) + m.Get("", reqToken(), org.ListMembers) + m.Combo("/{username}").Get(reqToken(), org.IsMember). + Delete(reqToken(), reqOrgOwnership(), org.DeleteMember) }) m.Group("/public_members", func() { m.Get("", org.ListPublicMembers) m.Combo("/{username}").Get(org.IsPublicMember). - Put(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgMembership(), org.PublicizeMember). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgMembership(), org.ConcealMember) + Put(reqToken(), reqOrgMembership(), org.PublicizeMember). + Delete(reqToken(), reqOrgMembership(), org.ConcealMember) }) m.Group("/teams", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.ListTeams) - m.Post("", reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam) - m.Get("/search", reqToken(auth_model.AccessTokenScopeReadOrg), org.SearchTeam) + m.Get("", reqToken(), org.ListTeams) + m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateTeamOption{}), org.CreateTeam) + m.Get("/search", reqToken(), org.SearchTeam) }, reqOrgMembership()) m.Group("/labels", func() { m.Get("", org.ListLabels) - m.Post("", reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel) - m.Combo("/{id}").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetLabel). - Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteLabel) + m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel) + m.Combo("/{id}").Get(reqToken(), org.GetLabel). + Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel). + Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel) }) m.Group("/hooks", func() { m.Combo("").Get(org.ListHooks). @@ -1252,29 +1319,29 @@ func Routes(ctx gocontext.Context) *web.Route { m.Combo("/{id}").Get(org.GetHook). Patch(bind(api.EditHookOption{}), org.EditHook). Delete(org.DeleteHook) - }, reqToken(auth_model.AccessTokenScopeAdminOrgHook), reqOrgOwnership(), reqWebhooksEnabled()) + }, reqToken(), reqOrgOwnership(), reqWebhooksEnabled()) m.Get("/activities/feeds", org.ListOrgActivityFeeds) - }, orgAssignment(true)) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) m.Group("/teams/{teamid}", func() { - m.Combo("").Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeam). - Patch(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.DeleteTeam) + m.Combo("").Get(reqToken(), org.GetTeam). + Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). + Delete(reqToken(), reqOrgOwnership(), org.DeleteTeam) m.Group("/members", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamMembers) + m.Get("", reqToken(), org.GetTeamMembers) m.Combo("/{username}"). - Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamMember). - Put(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.AddTeamMember). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), reqOrgOwnership(), org.RemoveTeamMember) + Get(reqToken(), org.GetTeamMember). + Put(reqToken(), reqOrgOwnership(), org.AddTeamMember). + Delete(reqToken(), reqOrgOwnership(), org.RemoveTeamMember) }) m.Group("/repos", func() { - m.Get("", reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamRepos) + m.Get("", reqToken(), org.GetTeamRepos) m.Combo("/{org}/{reponame}"). - Put(reqToken(auth_model.AccessTokenScopeWriteOrg), org.AddTeamRepository). - Delete(reqToken(auth_model.AccessTokenScopeWriteOrg), org.RemoveTeamRepository). - Get(reqToken(auth_model.AccessTokenScopeReadOrg), org.GetTeamRepo) + Put(reqToken(), org.AddTeamRepository). + Delete(reqToken(), org.RemoveTeamRepository). + Get(reqToken(), org.GetTeamRepo) }) m.Get("/activities/feeds", org.ListTeamActivityFeeds) - }, orgAssignment(false, true), reqToken(""), reqTeamMembership()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership()) m.Group("/admin", func() { m.Group("/cron", func() { @@ -1314,11 +1381,11 @@ func Routes(ctx gocontext.Context) *web.Route { Patch(bind(api.EditHookOption{}), admin.EditHook). Delete(admin.DeleteHook) }) - }, reqToken(auth_model.AccessTokenScopeSudo), reqSiteAdmin()) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin()) m.Group("/topics", func() { m.Get("/search", repo.TopicSearch) - }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) }, sudo()) return m diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index b6ebd25915..f4e9ac86a1 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -152,7 +152,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { return } - context.CheckRepoScopedToken(ctx, repo) + context.CheckRepoScopedToken(ctx, repo, auth_model.GetScopeLevelFromAccessMode(accessMode)) if ctx.Written() { return } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index ac935e51bb..f9e9ca5e52 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -89,6 +89,7 @@ func DeleteApplication(ctx *context.Context) { } func loadApplicationsData(ctx *context.Context) { + ctx.Data["AccessTokenScopePublicOnly"] = auth_model.AccessTokenScopePublicOnly tokens, err := auth_model.ListAccessTokens(auth_model.ListAccessTokensOptions{UserID: ctx.Doer.ID}) if err != nil { ctx.ServerError("ListAccessTokens", err) @@ -96,6 +97,7 @@ func loadApplicationsData(ctx *context.Context) { } ctx.Data["Tokens"] = tokens ctx.Data["EnableOAuth2"] = setting.OAuth2.Enable + ctx.Data["IsAdmin"] = ctx.Doer.IsAdmin if setting.OAuth2.Enable { ctx.Data["Applications"], err = auth_model.GetOAuth2ApplicationsByUserID(ctx, ctx.Doer.ID) if err != nil { diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go index 84efa25d53..66050187c9 100644 --- a/services/forms/user_form_test.go +++ b/services/forms/user_form_test.go @@ -112,12 +112,12 @@ func TestNewAccessTokenForm_GetScope(t *testing.T) { expectedErr error }{ { - form: NewAccessTokenForm{Name: "test", Scope: []string{"repo"}}, - scope: "repo", + form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}}, + scope: "read:repository", }, { - form: NewAccessTokenForm{Name: "test", Scope: []string{"repo", "user"}}, - scope: "repo,user", + form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}}, + scope: "read:repository,write:user", }, } diff --git a/services/lfs/locks.go b/services/lfs/locks.go index 1e5db6bd20..08d7432656 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/context" @@ -58,7 +59,7 @@ func GetListLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) - context.CheckRepoScopedToken(ctx, repository) + context.CheckRepoScopedToken(ctx, repository, auth_model.Read) if ctx.Written() { return } @@ -150,7 +151,7 @@ func PostLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) - context.CheckRepoScopedToken(ctx, repository) + context.CheckRepoScopedToken(ctx, repository, auth_model.Write) if ctx.Written() { return } @@ -222,7 +223,7 @@ func VerifyLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) - context.CheckRepoScopedToken(ctx, repository) + context.CheckRepoScopedToken(ctx, repository, auth_model.Read) if ctx.Written() { return } @@ -293,7 +294,7 @@ func UnLockHandler(ctx *context.Context) { } repository.MustOwner(ctx) - context.CheckRepoScopedToken(ctx, repository) + context.CheckRepoScopedToken(ctx, repository, auth_model.Write) if ctx.Written() { return } diff --git a/services/lfs/server.go b/services/lfs/server.go index 64e1203394..1f82aed54b 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -18,6 +18,7 @@ import ( "strings" actions_model "code.gitea.io/gitea/models/actions" + auth_model "code.gitea.io/gitea/models/auth" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -423,7 +424,12 @@ func getAuthenticatedRepository(ctx *context.Context, rc *requestContext, requir return nil } - context.CheckRepoScopedToken(ctx, repository) + if requireWrite { + context.CheckRepoScopedToken(ctx, repository, auth_model.Write) + } else { + context.CheckRepoScopedToken(ctx, repository, auth_model.Read) + } + if ctx.Written() { return nil } diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index 1c12a0a2b7..2b7db82dae 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -18,13 +18,21 @@ </div> <i class="text {{if .HasRecentActivity}}green{{end}}" {{if .HasRecentActivity}}data-tooltip-content="{{$.locale.Tr "settings.token_state_desc"}}"{{end}}>{{svg "fontawesome-send" 36}}</i> <div class="content"> - <!--Temporarily disable--> - <strong>{{.Name}}</strong> - <details class="gt-hidden"><summary><strong>{{.Name}}</strong></summary> - <p class="gt-my-2">{{$.locale.Tr "settings.scopes_list"}}</p> + <details><summary><strong>{{.Name}}</strong></summary> + <p class="gt-my-2"> + {{$.locale.Tr "settings.repo_and_org_access"}}: + {{if .DisplayPublicOnly}} + {{$.locale.Tr "settings.permissions_public_only"}} + {{else}} + {{$.locale.Tr "settings.permissions_access_all"}} + {{end}} + </p> + <p class="gt-my-2">{{$.locale.Tr "settings.permissions_list"}}</p> <ul class="gt-my-2"> {{range .Scope.StringSlice}} - <li>{{.}}</li> + {{if (ne . $.AccessTokenScopePublicOnly)}} + <li>{{.}}</li> + {{end}} {{end}} </ul> </details> @@ -40,222 +48,46 @@ <h5 class="ui top header"> {{.locale.Tr "settings.generate_new_token"}} </h5> - <p>{{.locale.Tr "settings.new_token_desc"}}</p> - <form class="ui form ignore-dirty" action="{{.Link}}" method="post"> + <form id="scoped-access-form" class="ui form ignore-dirty" action="{{.Link}}" method="post"> {{.CsrfTokenHtml}} <div class="field {{if .Err_Name}}error{{end}}"> <label for="name">{{.locale.Tr "settings.token_name"}}</label> <input id="name" name="name" value="{{.name}}" autofocus required maxlength="255"> </div> - <!--Temporarily disable--> - <details class="gt-hidden ui optional field"> - <summary class="gt-p-2"> - {{.locale.Tr "settings.select_scopes"}} + <div class="field"> + <label>{{.locale.Tr "settings.repo_and_org_access"}}</label> + <label class="gt-cursor-pointer"> + <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="{{$.AccessTokenScopePublicOnly}}"> + {{.locale.Tr "settings.permissions_public_only"}} + </label> + <label class="gt-cursor-pointer"> + <input class="enable-system gt-mt-2 gt-mr-2" type="radio" name="scope" value="" checked> + {{.locale.Tr "settings.permissions_access_all"}} + </label> + </div> + <details class="ui optional field"> + <summary class="gt-pb-4 gt-pl-2"> + {{.locale.Tr "settings.select_permissions"}} </summary> - <div class="field gt-pl-2"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="repo"> - <label>repo</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="repo:status"> - <label>repo:status</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="public_repo"> - <label>public_repo</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:org"> - <label>admin:org</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:org"> - <label>write:org</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:org"> - <label>read:org</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:public_key"> - <label>admin:public_key</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:public_key"> - <label>write:public_key</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:public_key"> - <label>read:public_key</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:repo_hook"> - <label>admin:repo_hook</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:repo_hook"> - <label>write:repo_hook</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:repo_hook"> - <label>read:repo_hook</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:org_hook"> - <label>admin:org_hook</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:user_hook"> - <label>admin:user_hook</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="notification"> - <label>notification</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="user"> - <label>user</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:user"> - <label>read:user</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="user:email"> - <label>user:email</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="user:follow"> - <label>user:follow</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="delete_repo"> - <label>delete_repo</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="package"> - <label>package</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:package"> - <label>write:package</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:package"> - <label>read:package</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="delete:package"> - <label>delete:package</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:gpg_key"> - <label>admin:gpg_key</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:gpg_key"> - <label>write:gpg_key</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:gpg_key"> - <label>read:gpg_key</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="admin:application"> - <label>admin:application</label> - </div> - </div> - <div class="field gt-pl-4"> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="write:application"> - <label>write:application</label> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="read:application"> - <label>read:application</label> - </div> - </div> - </div> - <div class="field"> - <div class="ui checkbox"> - <input class="enable-system" type="checkbox" name="scope" value="sudo"> - <label>sudo</label> - </div> - </div> + <div class="activity meta"> + <i>{{$.locale.Tr "settings.scoped_token_desc" (printf `href="/api/swagger" target="_blank"`) (printf `href="https://docs.gitea.com/development/oauth2-provider#scopes" target="_blank"`) | Str2html}}</i> + </div> + <scoped-access-token-category category="activitypub"></scoped-access-token-category> + {{if .IsAdmin}} + <scoped-access-token-category category="admin"></scoped-access-token-category> + {{end}} + <scoped-access-token-category category="issue"></scoped-access-token-category> + <scoped-access-token-category category="misc"></scoped-access-token-category> + <scoped-access-token-category category="notification"></scoped-access-token-category> + <scoped-access-token-category category="organization"></scoped-access-token-category> + <scoped-access-token-category category="package"></scoped-access-token-category> + <scoped-access-token-category category="repository"></scoped-access-token-category> + <scoped-access-token-category category="user"></scoped-access-token-category> </details> - <button class="ui green button"> + <div id="scoped-access-warning" class="ui warning message center gt-db gt-hidden"> + {{.locale.Tr "settings.at_least_one_permission"}} + </div> + <button id="scoped-access-submit" class="ui green button"> {{.locale.Tr "settings.generate_token"}} </button> </form> diff --git a/tests/integration/api_admin_org_test.go b/tests/integration/api_admin_org_test.go index 89617f7a2c..0bf4b1f7cb 100644 --- a/tests/integration/api_admin_org_test.go +++ b/tests/integration/api_admin_org_test.go @@ -21,7 +21,7 @@ import ( func TestAPIAdminOrgCreate(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeSudo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) org := api.CreateOrgOption{ UserName: "user2_org", @@ -55,7 +55,7 @@ func TestAPIAdminOrgCreate(t *testing.T) { func TestAPIAdminOrgCreateBadVisibility(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeSudo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) org := api.CreateOrgOption{ UserName: "user2_org", diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index 7cfc3276ee..5900811922 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -25,7 +25,7 @@ func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) { session := loginUser(t, "user1") keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeSudo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", keyOwner.Name, token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", @@ -52,7 +52,7 @@ func TestAPIAdminDeleteMissingSSHKey(t *testing.T) { defer tests.PrepareTestEnv(t)() // user1 is an admin user - token := getUserToken(t, "user1", auth_model.AccessTokenScopeSudo) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin) req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d?token=%s", unittest.NonexistentID, token) MakeRequest(t, req, http.StatusNotFound) } @@ -61,7 +61,7 @@ func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys?token=%s", adminUsername, token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ @@ -82,7 +82,7 @@ func TestAPISudoUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" normalUsername := "user2" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", normalUsername, token) req := NewRequest(t, "GET", urlStr) @@ -98,7 +98,7 @@ func TestAPISudoUserForbidden(t *testing.T) { adminUsername := "user1" normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) urlStr := fmt.Sprintf("/api/v1/user?sudo=%s&token=%s", adminUsername, token) req := NewRequest(t, "GET", urlStr) MakeRequest(t, req, http.StatusForbidden) @@ -107,7 +107,7 @@ func TestAPISudoUserForbidden(t *testing.T) { func TestAPIListUsers(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token) req := NewRequest(t, "GET", urlStr) @@ -143,7 +143,7 @@ func TestAPIListUsersNonAdmin(t *testing.T) { func TestAPICreateUserInvalidEmail(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "email": "invalid_email@domain.com\r\n", @@ -161,7 +161,7 @@ func TestAPICreateUserInvalidEmail(t *testing.T) { func TestAPICreateAndDeleteUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) req := NewRequestWithValues( t, @@ -187,7 +187,7 @@ func TestAPICreateAndDeleteUser(t *testing.T) { func TestAPIEditUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users/%s?token=%s", "user2", token) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -229,7 +229,7 @@ func TestAPIEditUser(t *testing.T) { func TestAPICreateRepoForUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) req := NewRequestWithJSON( t, @@ -245,7 +245,7 @@ func TestAPICreateRepoForUser(t *testing.T) { func TestAPIRenameUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" - token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeSudo) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename?token=%s", "user2", token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ // required diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 6616399a8a..dd81ec22dd 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -16,7 +16,7 @@ import ( ) func testAPIGetBranch(t *testing.T, branchName string, exists bool) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token) resp := MakeRequest(t, req, NoExpectedStatus) if !exists { @@ -32,7 +32,7 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { } func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) resp := MakeRequest(t, req, expectedHTTPStatus) @@ -44,7 +44,7 @@ func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPSta } func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/branch_protections?token="+token, &api.BranchProtection{ RuleName: branchName, }) @@ -58,7 +58,7 @@ func testAPICreateBranchProtection(t *testing.T, branchName string, expectedHTTP } func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.BranchProtection, expectedHTTPStatus int) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/user2/repo1/branch_protections/"+branchName+"?token="+token, body) resp := MakeRequest(t, req, expectedHTTPStatus) @@ -70,13 +70,13 @@ func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.Bran } func testAPIDeleteBranchProtection(t *testing.T, branchName string, expectedHTTPStatus int) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branch_protections/%s?token=%s", branchName, token) MakeRequest(t, req, expectedHTTPStatus) } func testAPIDeleteBranch(t *testing.T, branchName string, expectedHTTPStatus int) { - token := getUserToken(t, "user2", auth_model.AccessTokenScopeRepo) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "DELETE", "/api/v1/repos/user2/repo1/branches/%s?token=%s", branchName, token) MakeRequest(t, req, expectedHTTPStatus) } @@ -102,7 +102,7 @@ func TestAPICreateBranch(t *testing.T) { func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { username := "user2" - ctx := NewAPITestContext(t, username, "my-noo-repo", auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, username, "my-noo-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() t.Run("CreateRepo", doAPICreateRepository(ctx, false)) @@ -149,7 +149,7 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { } func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBranch, newBranch string, status int) bool { - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "POST", "/api/v1/repos/"+user+"/"+repo+"/branches?token="+token, &api.CreateBranchRepoOption{ BranchName: newBranch, OldBranchName: oldBranch, diff --git a/tests/integration/api_comment_attachment_test.go b/tests/integration/api_comment_attachment_test.go index 1d0b902b39..8aa73dd368 100644 --- a/tests/integration/api_comment_attachment_test.go +++ b/tests/integration/api_comment_attachment_test.go @@ -36,8 +36,8 @@ func TestAPIGetCommentAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token) session.MakeRequest(t, req, http.StatusOK) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token) resp := session.MakeRequest(t, req, http.StatusOK) @@ -61,8 +61,9 @@ func TestAPIListCommentAttachments(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets", - repoOwner.Name, repo.Name, comment.ID) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets?token=%s", + repoOwner.Name, repo.Name, comment.ID, token) resp := session.MakeRequest(t, req, http.StatusOK) var apiAttachments []*api.Attachment @@ -82,7 +83,7 @@ func TestAPICreateCommentAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets?token=%s", repoOwner.Name, repo.Name, comment.ID, token) @@ -121,7 +122,7 @@ func TestAPIEditCommentAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -144,7 +145,7 @@ func TestAPIDeleteCommentAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token) diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 56de8b5909..c773afab3d 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -76,7 +76,7 @@ func TestAPIListIssueComments(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/comments?token=%s", repoOwner.Name, repo.Name, issue.Index, token) resp := MakeRequest(t, req, http.StatusOK) @@ -96,7 +96,7 @@ func TestAPICreateComment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments?token=%s", repoOwner.Name, repo.Name, issue.Index, token) req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ @@ -118,7 +118,7 @@ func TestAPIGetComment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeReadIssue) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d", repoOwner.Name, repo.Name, comment.ID) MakeRequest(t, req, http.StatusOK) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, token) @@ -146,7 +146,7 @@ func TestAPIEditComment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, token) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -170,7 +170,7 @@ func TestAPIDeleteComment(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue) req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, token) MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go index f66961786f..a4545bd0bb 100644 --- a/tests/integration/api_gpg_keys_test.go +++ b/tests/integration/api_gpg_keys_test.go @@ -21,8 +21,8 @@ type makeRequestFunc func(testing.TB, *http.Request, int) *httptest.ResponseReco func TestGPGKeys(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) - tokenWithGPGKeyScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminGPGKey, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + tokenWithGPGKeyScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) tt := []struct { name string @@ -36,7 +36,7 @@ func TestGPGKeys(t *testing.T) { }, { name: "LoggedAsUser2", makeRequest: session.MakeRequest, token: token, - results: []int{http.StatusForbidden, http.StatusOK, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, + results: []int{http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden}, }, { name: "LoggedAsUser2WithScope", makeRequest: session.MakeRequest, token: tokenWithGPGKeyScope, diff --git a/tests/integration/api_httpsig_test.go b/tests/integration/api_httpsig_test.go index 57f83490dc..4520364527 100644 --- a/tests/integration/api_httpsig_test.go +++ b/tests/integration/api_httpsig_test.go @@ -53,7 +53,7 @@ func TestHTTPSigPubKey(t *testing.T) { // Add our public key to user1 defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminPublicKey, auth_model.AccessTokenScopeSudo)) + token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token) keyType := "ssh-rsa" keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABAQCqOZB5vkRvXFXups1/0StDRdG8plbNSwsWEnNnP4Bvurxa0+z3W9B8GLKnDiLw5MbpbMNyBlpXw13GfuIeciy10DWTz0xUbiy3J3KabCaT36asIw2y7k6Z0jL0UBnrVENwq5/lUbZYqSZ4rRU744wkhh8TULpzM14npQCZwg6aEbG+MwjzddQ72fR+3BPBrKn5dTmmu8rH99O+U+Nuto81Tg7PA+NUupcHOmhdiEGq49plgVFXK98Vks5tiybL4GuzFyWgyX73Dg/QBMn2eMHt1EMv5Gs3i6GFhKKGo4rjDi9qI6PX5oDR4LTNe6cR8td8YhVD8WFZwLLl/vaYyIqd" @@ -69,7 +69,8 @@ func TestHTTPSigPubKey(t *testing.T) { keyID := ssh.FingerprintSHA256(sshSigner.PublicKey()) // create the request - req = NewRequest(t, "GET", "/api/v1/admin/users") + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/users?token=%s", token)) signer, _, err := httpsig.NewSSHSigner(sshSigner, httpsig.DigestSha512, []string{httpsig.RequestTarget, "(created)", "(expires)"}, httpsig.Signature, 10) if err != nil { diff --git a/tests/integration/api_issue_attachment_test.go b/tests/integration/api_issue_attachment_test.go index b4d6dab42a..3b43ba2c41 100644 --- a/tests/integration/api_issue_attachment_test.go +++ b/tests/integration/api_issue_attachment_test.go @@ -32,7 +32,7 @@ func TestAPIGetIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, issue.Index, attachment.ID, token) @@ -53,7 +53,7 @@ func TestAPIListIssueAttachments(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets?token=%s", repoOwner.Name, repo.Name, issue.Index, token) @@ -73,7 +73,7 @@ func TestAPICreateIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets?token=%s", repoOwner.Name, repo.Name, issue.Index, token) @@ -111,7 +111,7 @@ func TestAPIEditIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, issue.Index, attachment.ID, token) req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -133,7 +133,7 @@ func TestAPIDeleteIssueAttachment(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, issue.Index, attachment.ID, token) diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index 1824015983..d2d8af102b 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -25,7 +25,7 @@ func TestAPIModifyLabels(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token) // CreateLabel @@ -97,7 +97,7 @@ func TestAPIAddIssueLabels(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels?token=%s", repo.OwnerName, repo.Name, issue.Index, token) req := NewRequestWithJSON(t, "POST", urlStr, &api.IssueLabelsOption{ @@ -120,7 +120,7 @@ func TestAPIReplaceIssueLabels(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels?token=%s", owner.Name, repo.Name, issue.Index, token) req := NewRequestWithJSON(t, "PUT", urlStr, &api.IssueLabelsOption{ @@ -144,7 +144,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) user := "user1" session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeAdminOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization) urlStr := fmt.Sprintf("/api/v1/orgs/%s/labels?token=%s", owner.Name, token) // CreateLabel diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index cbce795bc9..3bd763f4b5 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -29,7 +29,7 @@ func TestAPIIssuesMilestone(t *testing.T) { assert.Equal(t, structs.StateOpen, milestone.State()) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // update values of issue milestoneState := "closed" diff --git a/tests/integration/api_issue_pin_test.go b/tests/integration/api_issue_pin_test.go index 65be1d74f2..5a9efc058b 100644 --- a/tests/integration/api_issue_pin_test.go +++ b/tests/integration/api_issue_pin_test.go @@ -29,7 +29,7 @@ func TestAPIPinIssue(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s", @@ -56,7 +56,7 @@ func TestAPIUnpinIssue(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s", @@ -97,7 +97,7 @@ func TestAPIMoveIssuePin(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the first Issue urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s", @@ -152,7 +152,7 @@ func TestAPIListPinnedIssues(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Pin the Issue urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin?token=%s", diff --git a/tests/integration/api_issue_reaction_test.go b/tests/integration/api_issue_reaction_test.go index 42793c66cf..7d3ee2d154 100644 --- a/tests/integration/api_issue_reaction_test.go +++ b/tests/integration/api_issue_reaction_test.go @@ -29,7 +29,7 @@ func TestAPIIssuesReactions(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions?token=%s", @@ -88,7 +88,7 @@ func TestAPICommentReactions(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue.Repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go index a8a832414d..09d404ce4e 100644 --- a/tests/integration/api_issue_stopwatch_test.go +++ b/tests/integration/api_issue_stopwatch_test.go @@ -26,7 +26,7 @@ func TestAPIListStopWatches(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadUser) req := NewRequestf(t, "GET", "/api/v1/user/stopwatches?token=%s", token) resp := MakeRequest(t, req, http.StatusOK) var apiWatches []*api.StopWatch @@ -52,7 +52,7 @@ func TestAPIStopStopWatches(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/stop?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) MakeRequest(t, req, http.StatusCreated) @@ -68,7 +68,7 @@ func TestAPICancelStopWatches(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/stopwatch/delete?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) MakeRequest(t, req, http.StatusNoContent) @@ -84,7 +84,7 @@ func TestAPIStartStopWatches(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/issues/%d/stopwatch/start?token=%s", owner.Name, issue.Repo.Name, issue.Index, token) MakeRequest(t, req, http.StatusCreated) diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 473e720754..09c83b63b1 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -31,7 +31,7 @@ func TestAPIIssueSubscriptions(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: issue1.PosterID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) testSubscription := func(issue *issues_model.Issue, isWatching bool) { issueRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index 4344c15ea4..324f5ddbee 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -30,7 +30,7 @@ func TestAPIListIssues(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repo.Name)) link.RawQuery = url.Values{"token": {token}, "state": {"all"}}.Encode() @@ -81,7 +81,7 @@ func TestAPICreateIssue(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ Body: body, @@ -117,7 +117,7 @@ func TestAPIEditIssue(t *testing.T) { assert.Equal(t, api.StateOpen, issueBefore.State()) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // update values of issue issueState := "closed" @@ -171,7 +171,7 @@ func TestAPIEditIssue(t *testing.T) { func TestAPISearchIssues(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2") + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadIssue) // as this API was used in the frontend, it uses UI page size expectedIssueCount := 16 // from the fixtures @@ -180,7 +180,7 @@ func TestAPISearchIssues(t *testing.T) { } link, _ := url.Parse("/api/v1/repos/issues/search") - query := url.Values{"token": {getUserToken(t, "user1")}} + query := url.Values{"token": {getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)}} var apiIssues []*api.Issue link.RawQuery = query.Encode() @@ -278,7 +278,7 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { } link, _ := url.Parse("/api/v1/repos/issues/search") - query := url.Values{"token": {getUserToken(t, "user1")}} + query := url.Values{"token": {getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue)}} var apiIssues []*api.Issue link.RawQuery = query.Encode() diff --git a/tests/integration/api_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go index 7d9c785474..d3e45456a6 100644 --- a/tests/integration/api_issue_tracked_time_test.go +++ b/tests/integration/api_issue_tracked_time_test.go @@ -28,7 +28,7 @@ func TestAPIGetTrackedTimes(t *testing.T) { assert.NoError(t, issue2.LoadRepo(db.DefaultContext)) session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/%d/times?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, token) resp := MakeRequest(t, req, http.StatusOK) @@ -71,7 +71,7 @@ func TestAPIDeleteTrackedTime(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) // Deletion not allowed req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/%d/times/%d?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, time6.ID, token) @@ -106,7 +106,7 @@ func TestAPIAddTrackedTimes(t *testing.T) { admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, admin.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/times?token=%s", user2.Name, issue2.Repo.Name, issue2.Index, token) diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index dc25cbfc1a..238c3cb823 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -54,7 +54,7 @@ func TestCreateReadOnlyDeployKey(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) rawKeyBody := api.CreateKeyOption{ Title: "read-only", @@ -80,7 +80,7 @@ func TestCreateReadWriteDeployKey(t *testing.T) { repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) keysURL := fmt.Sprintf("/api/v1/repos/%s/%s/keys?token=%s", repoOwner.Name, repo.Name, token) rawKeyBody := api.CreateKeyOption{ Title: "read-write", @@ -104,7 +104,7 @@ func TestCreateUserKey(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"}) session := loginUser(t, "user1") - token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminPublicKey)) + token := url.QueryEscape(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)) keysURL := fmt.Sprintf("/api/v1/user/keys?token=%s", token) keyType := "ssh-rsa" keyContent := "AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM=" @@ -168,7 +168,7 @@ func TestCreateUserKey(t *testing.T) { // Now login as user 2 session2 := loginUser(t, "user2") - token2 := url.QueryEscape(getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeAdminPublicKey)) + token2 := url.QueryEscape(getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser)) // Should find key even though not ours, but we shouldn't know whose it is fingerprintURL = fmt.Sprintf("/api/v1/user/keys?token=%s&fingerprint=%s", token2, newPublicKey.Fingerprint) diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index 0ff13704cf..52d6e6d84a 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -28,7 +28,7 @@ func TestAPINotification(t *testing.T) { thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) assert.NoError(t, thread5.LoadAttributes(db.DefaultContext)) session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeNotification) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteNotification, auth_model.AccessTokenScopeWriteRepository) // -- GET /notifications -- // test filter @@ -146,7 +146,7 @@ func TestAPINotificationPUT(t *testing.T) { thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) assert.NoError(t, thread5.LoadAttributes(db.DefaultContext)) session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeNotification) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteNotification) // Check notifications are as expected req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?all=true&token=%s", token)) diff --git a/tests/integration/api_oauth2_apps_test.go b/tests/integration/api_oauth2_apps_test.go index c320efb391..72cdba2ea2 100644 --- a/tests/integration/api_oauth2_apps_test.go +++ b/tests/integration/api_oauth2_apps_test.go @@ -55,7 +55,7 @@ func testAPICreateOAuth2Application(t *testing.T) { func testAPIListOAuth2Applications(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadApplication) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) existApp := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ UID: user.ID, @@ -86,7 +86,7 @@ func testAPIListOAuth2Applications(t *testing.T) { func testAPIDeleteOAuth2Application(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteApplication) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) oldApp := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ UID: user.ID, @@ -107,7 +107,7 @@ func testAPIDeleteOAuth2Application(t *testing.T) { func testAPIGetOAuth2Application(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadApplication) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) existApp := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ UID: user.ID, diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index 4b79b32c59..edbf576b9e 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -26,7 +26,7 @@ import ( func TestAPIOrgCreate(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrg) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) org := api.CreateOrgOption{ UserName: "user1_org", @@ -100,7 +100,7 @@ func TestAPIOrgEdit(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) org := api.EditOrgOption{ FullName: "User3 organization new full name", Description: "A new description", @@ -127,7 +127,7 @@ func TestAPIOrgEditBadVisibility(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) org := api.EditOrgOption{ FullName: "User3 organization new full name", Description: "A new description", @@ -162,7 +162,7 @@ func TestAPIOrgDeny(t *testing.T) { func TestAPIGetAll(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrg) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) // accessing with a token will return all orgs req := NewRequestf(t, "GET", "/api/v1/orgs?token=%s", token) @@ -186,7 +186,7 @@ func TestAPIGetAll(t *testing.T) { func TestAPIOrgSearchEmptyTeam(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { - token := getUserToken(t, "user1", auth_model.AccessTokenScopeAdminOrg) + token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) orgName := "org_with_empty_team" // create org diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go index 78389b5740..433b183c29 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -28,7 +28,7 @@ func TestPackageNpm(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopePackage)) + token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage)) packageName := "@scope/test-package" packageVersion := "1.0.1-pre" diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 2240d2a5d4..a6c4090f0e 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -75,7 +75,7 @@ func TestPackageNuGet(t *testing.T) { } user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user.Name, auth_model.AccessTokenScopePackage) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage) packageName := "test.package" packageVersion := "1.0.3" diff --git a/tests/integration/api_packages_pub_test.go b/tests/integration/api_packages_pub_test.go index 5c1cc6052f..df0c7aa21b 100644 --- a/tests/integration/api_packages_pub_test.go +++ b/tests/integration/api_packages_pub_test.go @@ -31,7 +31,7 @@ func TestPackagePub(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := "Bearer " + getUserToken(t, user.Name, auth_model.AccessTokenScopePackage) + token := "Bearer " + getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage) packageName := "test_package" packageVersion := "1.0.1" diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 74a7e3c795..84733f683b 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -34,7 +34,7 @@ func TestPackageAPI(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user.Name) tokenReadPackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) - tokenDeletePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeDeletePackage) + tokenDeletePackage := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWritePackage) packageName := "test-package" packageVersion := "1.0.3" diff --git a/tests/integration/api_packages_vagrant_test.go b/tests/integration/api_packages_vagrant_test.go index b28bfca6f0..cbfc362f32 100644 --- a/tests/integration/api_packages_vagrant_test.go +++ b/tests/integration/api_packages_vagrant_test.go @@ -28,7 +28,7 @@ func TestPackageVagrant(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := "Bearer " + getUserToken(t, user.Name, auth_model.AccessTokenScopePackage) + token := "Bearer " + getUserToken(t, user.Name, auth_model.AccessTokenScopeWritePackage) packageName := "test_package" packageVersion := "1.0.1" diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index e0abf34f5e..4718f115cb 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -28,7 +28,7 @@ func TestAPIPullReview(t *testing.T) { // test ListPullReviews session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token) resp := MakeRequest(t, req, http.StatusOK) @@ -231,7 +231,7 @@ func TestAPIPullReviewRequest(t *testing.T) { // Test add Review Request session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.PullReviewRequestOptions{ Reviewers: []string{"user4@example.com", "user8"}, }) @@ -251,7 +251,7 @@ func TestAPIPullReviewRequest(t *testing.T) { // Test Remove Review Request session2 := loginUser(t, "user4") - token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository) req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token2), &api.PullReviewRequestOptions{ Reviewers: []string{"user4"}, diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index cf675f9740..9d590630e4 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -29,7 +29,7 @@ func TestAPIViewPulls(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) - ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, "user2", repo.Name, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls?state=all&token="+ctx.Token, owner.Name, repo.Name) resp := ctx.Session.MakeRequest(t, req, http.StatusOK) @@ -75,7 +75,7 @@ func TestAPIMergePullWIP(t *testing.T) { assert.Contains(t, pr.Issue.Title, setting.Repository.PullRequest.WorkInProgressPrefixes[0]) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge?token=%s", owner.Name, repo.Name, pr.Index, token), &forms.MergePullRequestForm{ MergeMessageField: pr.Issue.Title, Do: string(repo_model.MergeStyleMerge), @@ -94,7 +94,7 @@ func TestAPICreatePullSuccess(t *testing.T) { owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) session := loginUser(t, owner11.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ Head: fmt.Sprintf("%s:master", owner11.Name), Base: "master", @@ -114,7 +114,7 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) session := loginUser(t, owner11.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ Head: fmt.Sprintf("%s:master", owner11.Name), @@ -151,7 +151,7 @@ func TestAPICreatePullWithFieldsFailure(t *testing.T) { owner11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo11.OwnerID}) session := loginUser(t, owner11.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ Head: fmt.Sprintf("%s:master", owner11.Name), @@ -181,7 +181,7 @@ func TestAPIEditPull(t *testing.T) { owner10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo10.OwnerID}) session := loginUser(t, owner10.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", owner10.Name, repo10.Name, token), &api.CreatePullRequestOption{ Head: "develop", Base: "master", diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index aa5816ad02..7f43939083 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -25,7 +25,7 @@ func TestAPIListReleases(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeRepo) + token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeReadRepository) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/releases", user2.Name, repo.Name)) link.RawQuery = url.Values{"token": {token}}.Encode() @@ -101,7 +101,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath()) assert.NoError(t, err) @@ -153,7 +153,7 @@ func TestAPICreateReleaseToDefaultBranch(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") } @@ -164,7 +164,7 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) gitRepo, err := git.OpenRepository(git.DefaultContext, repo.RepoPath()) assert.NoError(t, err) @@ -232,7 +232,7 @@ func TestAPIDeleteReleaseByTagName(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) createNewReleaseUsingAPI(t, session, token, owner, repo, "release-tag", "", "Release Tag", "test") diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index 699d7a436c..5d1db1b09b 100644 --- a/tests/integration/api_repo_archive_test.go +++ b/tests/integration/api_repo_archive_test.go @@ -25,7 +25,7 @@ func TestAPIDownloadArchive(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user2.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.zip", user2.Name, repo.Name)) link.RawQuery = url.Values{"token": {token}}.Encode() diff --git a/tests/integration/api_repo_collaborator_test.go b/tests/integration/api_repo_collaborator_test.go index ed01538477..b7280a4f6c 100644 --- a/tests/integration/api_repo_collaborator_test.go +++ b/tests/integration/api_repo_collaborator_test.go @@ -28,7 +28,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}) - testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name, auth_model.AccessTokenScopeWriteRepository) t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, repo2Owner.Name, testCtx.Token) @@ -85,7 +85,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeRepo) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) @@ -100,7 +100,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead)) _session := loginUser(t, user5.Name) - _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeRepo) + _testCtx := NewAPITestContext(t, user5.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.Name, _testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) @@ -116,7 +116,7 @@ func TestAPIRepoCollaboratorPermission(t *testing.T) { t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead)) _session := loginUser(t, user10.Name) - _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeRepo) + _testCtx := NewAPITestContext(t, user10.Name, repo2.Name, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user11.Name, _testCtx.Token) resp := _session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_repo_edit_test.go b/tests/integration/api_repo_edit_test.go index 30dc73ef1b..b7242fb316 100644 --- a/tests/integration/api_repo_edit_test.go +++ b/tests/integration/api_repo_edit_test.go @@ -147,10 +147,10 @@ func TestAPIRepoEdit(t *testing.T) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test editing a repo1 which user2 owns, changing name and many properties origRepoEditOption := getRepoEditOptionFromRepo(repo1) diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index a3a5422154..cbc164891b 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -151,10 +151,10 @@ func TestAPICreateFile(t *testing.T) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) // Test creating a file in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ @@ -280,7 +280,7 @@ func TestAPICreateFile(t *testing.T) { MakeRequest(t, req, http.StatusForbidden) // Test creating a file in an empty repository - doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeRepo), true)(t) + doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t) createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index ae28c97002..df9e801e2c 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -49,10 +49,10 @@ func TestAPIDeleteFile(t *testing.T) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test deleting a file in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go index a6a1e63439..4649babad1 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -25,7 +25,7 @@ func TestAPIGetRawFileOrLFS(t *testing.T) { // Test with LFS onGiteaRun(t, func(t *testing.T, u *url.URL) { - httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeDeleteRepo) + httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) doAPICreateRepository(httpContext, false, func(t *testing.T, repository api.Repository) { u.Path = httpContext.GitPath() dstPath := t.TempDir() diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index 177d7282ca..5bcb531fc1 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -117,10 +117,10 @@ func TestAPIUpdateFile(t *testing.T) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test updating a file in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index 38187ec5b9..8599b12a7f 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -72,10 +72,10 @@ func TestAPIChangeFiles(t *testing.T) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test changing files in repo1 which user2 owns, try both with branch and empty branch for _, branch := range [...]string{ diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 2c7b44120c..f91305abef 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "testing" + auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -64,10 +65,10 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Make a new branch in repo1 newBranch := "test_branch" diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index 8b193b03c9..be091cea4e 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -9,6 +9,7 @@ import ( "net/url" "testing" + auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -66,10 +67,10 @@ func testAPIGetContents(t *testing.T, u *url.URL) { // Get user2's token session := loginUser(t, user2.Name) - token2 := getTokenForLoggedInUser(t, session) + token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Get user4's token session = loginUser(t, user4.Name) - token4 := getTokenForLoggedInUser(t, session) + token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Make a new branch in repo1 newBranch := "test_branch" diff --git a/tests/integration/api_repo_git_blobs_test.go b/tests/integration/api_repo_git_blobs_test.go index 02652e5934..9a0bb6d0f2 100644 --- a/tests/integration/api_repo_git_blobs_test.go +++ b/tests/integration/api_repo_git_blobs_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -31,7 +32,7 @@ func TestAPIReposGitBlobs(t *testing.T) { // Login as User2. session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test a public repo that anyone can GET the blob of req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/git/blobs/%s", user2.Name, repo1.Name, repo1ReadmeSHA) diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index 90048a5496..765055720d 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" @@ -28,7 +29,7 @@ func TestAPIReposGitCommits(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // check invalid requests req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/commits/12345?token="+token, user.Name) @@ -56,7 +57,7 @@ func TestAPIReposGitCommitList(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting commits (Page 1) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?token="+token+"¬=master&sha=remove-files-a", user.Name) @@ -79,7 +80,7 @@ func TestAPIReposGitCommitListNotMaster(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting commits (Page 1) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token, user.Name) @@ -104,7 +105,7 @@ func TestAPIReposGitCommitListPage2Empty(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting commits (Page=2) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&page=2", user.Name) @@ -121,7 +122,7 @@ func TestAPIReposGitCommitListDifferentBranch(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting commits (Page=1, Branch=good-sign) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&sha=good-sign", user.Name) @@ -140,7 +141,7 @@ func TestAPIReposGitCommitListWithoutSelectFields(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting commits without files, verification, and stats req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?token="+token+"&sha=good-sign&stat=false&files=false&verification=false", user.Name) @@ -161,7 +162,7 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test getting diff reqDiff := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.diff?token="+token, user.Name) @@ -183,7 +184,7 @@ func TestGetFileHistory(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/commits?path=readme.md&token="+token+"&sha=good-sign", user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -203,7 +204,7 @@ func TestGetFileHistoryNotOnMaster(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo20/commits?path=test.csv&token="+token+"&sha=add-csv¬=master", user.Name) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_repo_git_hook_test.go b/tests/integration/api_repo_git_hook_test.go index e1c4682e6d..9f3205ce60 100644 --- a/tests/integration/api_repo_git_hook_test.go +++ b/tests/integration/api_repo_git_hook_test.go @@ -31,7 +31,7 @@ func TestAPIListGitHooks(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", owner.Name, repo.Name, token) resp := MakeRequest(t, req, http.StatusOK) @@ -57,7 +57,7 @@ func TestAPIListGitHooksNoHooks(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", owner.Name, repo.Name, token) resp := MakeRequest(t, req, http.StatusOK) @@ -77,7 +77,7 @@ func TestAPIListGitHooksNoAccess(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git?token=%s", owner.Name, repo.Name, token) MakeRequest(t, req, http.StatusForbidden) @@ -91,7 +91,7 @@ func TestAPIGetGitHook(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) resp := MakeRequest(t, req, http.StatusOK) @@ -108,7 +108,7 @@ func TestAPIGetGitHookNoAccess(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) MakeRequest(t, req, http.StatusForbidden) @@ -122,7 +122,7 @@ func TestAPIEditGitHook(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) @@ -151,7 +151,7 @@ func TestAPIEditGitHookNoAccess(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) req := NewRequestWithJSON(t, "PATCH", urlStr, &api.EditGitHookOption{ @@ -168,7 +168,7 @@ func TestAPIDeleteGitHook(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) @@ -190,7 +190,7 @@ func TestAPIDeleteGitHookNoAccess(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/hooks/git/pre-receive?token=%s", owner.Name, repo.Name, token) MakeRequest(t, req, http.StatusForbidden) diff --git a/tests/integration/api_repo_git_notes_test.go b/tests/integration/api_repo_git_notes_test.go index 1448f40572..30846f235f 100644 --- a/tests/integration/api_repo_git_notes_test.go +++ b/tests/integration/api_repo_git_notes_test.go @@ -8,6 +8,7 @@ import ( "net/url" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" @@ -20,7 +21,7 @@ func TestAPIReposGitNotes(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // check invalid requests req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/git/notes/12345?token=%s", user.Name, token) diff --git a/tests/integration/api_repo_git_ref_test.go b/tests/integration/api_repo_git_ref_test.go index aac01ca9ad..20900b3241 100644 --- a/tests/integration/api_repo_git_ref_test.go +++ b/tests/integration/api_repo_git_ref_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/tests" @@ -17,7 +18,7 @@ func TestAPIReposGitRefs(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) for _, ref := range [...]string{ "refs/heads/master", // Branch diff --git a/tests/integration/api_repo_git_tags_test.go b/tests/integration/api_repo_git_tags_test.go index b29fc45cf5..a9d47abf93 100644 --- a/tests/integration/api_repo_git_tags_test.go +++ b/tests/integration/api_repo_git_tags_test.go @@ -26,7 +26,7 @@ func TestAPIGitTags(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Set up git config for the tagger _ = git.NewCommand(git.DefaultContext, "config", "user.name").AddDynamicArguments(user.Name).Run(&git.RunOpts{Dir: repo.RepoPath()}) @@ -70,7 +70,7 @@ func TestAPIDeleteTagByName(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, owner.LowerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/tags/delete-tag?token=%s", owner.Name, repo.Name, token) diff --git a/tests/integration/api_repo_git_trees_test.go b/tests/integration/api_repo_git_trees_test.go index d1d49e4627..7a7ece120c 100644 --- a/tests/integration/api_repo_git_trees_test.go +++ b/tests/integration/api_repo_git_trees_test.go @@ -7,6 +7,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -28,7 +29,7 @@ func TestAPIReposGitTrees(t *testing.T) { // Login as User2. session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) // Test a public repo that anyone can GET the tree of for _, ref := range [...]string{ diff --git a/tests/integration/api_repo_hook_test.go b/tests/integration/api_repo_hook_test.go index 0fa2402992..18b12597db 100644 --- a/tests/integration/api_repo_hook_test.go +++ b/tests/integration/api_repo_hook_test.go @@ -26,7 +26,7 @@ func TestAPICreateHook(t *testing.T) { // user1 is an admin user session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepoHook) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) completeURL := func(lastSegment string) string { return fmt.Sprintf("/api/v1/repos/%s/%s/%s?token=%s", owner.Name, repo.Name, lastSegment, token) } diff --git a/tests/integration/api_repo_lfs_migrate_test.go b/tests/integration/api_repo_lfs_migrate_test.go index e66ca6b147..2cd7132b76 100644 --- a/tests/integration/api_repo_lfs_migrate_test.go +++ b/tests/integration/api_repo_lfs_migrate_test.go @@ -31,7 +31,7 @@ func TestAPIRepoLFSMigrateLocal(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ CloneAddr: path.Join(setting.RepoRootPath, "migration/lfs-test.git"), diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index a7a70baeef..b0e9269bb8 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -60,7 +60,7 @@ func TestAPILFSMediaType(t *testing.T) { } func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository { - ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepo", doAPICreateRepository(ctx, false)) repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo") diff --git a/tests/integration/api_repo_raw_test.go b/tests/integration/api_repo_raw_test.go index 60e9eeed6b..ccb20a939e 100644 --- a/tests/integration/api_repo_raw_test.go +++ b/tests/integration/api_repo_raw_test.go @@ -20,7 +20,7 @@ func TestAPIReposRaw(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) for _, ref := range [...]string{ "master", // Branch diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index d4fd9097dd..c4282f9928 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -23,7 +23,7 @@ func TestAPIRepoTags(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Login as User2. session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) repoName := "repo1" diff --git a/tests/integration/api_repo_teams_test.go b/tests/integration/api_repo_teams_test.go index 1f444e3141..3d3200b291 100644 --- a/tests/integration/api_repo_teams_test.go +++ b/tests/integration/api_repo_teams_test.go @@ -28,7 +28,7 @@ func TestAPIRepoTeams(t *testing.T) { // user4 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // ListTeams url := fmt.Sprintf("/api/v1/repos/%s/teams?token=%s", publicOrgRepo.FullName(), token) @@ -68,7 +68,7 @@ func TestAPIRepoTeams(t *testing.T) { // AddTeam with user2 user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session = loginUser(t, user.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) url = fmt.Sprintf("/api/v1/repos/%s/teams/%s?token=%s", publicOrgRepo.FullName(), "team1", token) req = NewRequest(t, "PUT", url) MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index aa4e674206..0f387192eb 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -189,7 +189,7 @@ func TestAPISearchRepo(t *testing.T) { if userToLogin != nil && userToLogin.ID > 0 { testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID) session := loginUser(t, userToLogin.Name) - token = getTokenForLoggedInUser(t, session) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) userID = userToLogin.ID } else { testName = "AnonymousUser" @@ -295,7 +295,7 @@ func TestAPIOrgRepos(t *testing.T) { for userToLogin, expected := range expectedResults { testName := fmt.Sprintf("LoggedUser%d", userToLogin.ID) session := loginUser(t, userToLogin.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) t.Run(testName, func(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos?token="+token, sourceOrg.Name) @@ -317,7 +317,7 @@ func TestAPIGetRepoByIDUnauthorized(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) req := NewRequestf(t, "GET", "/api/v1/repositories/2?token="+token) MakeRequest(t, req, http.StatusNotFound) } @@ -341,7 +341,7 @@ func TestAPIRepoMigrate(t *testing.T) { for _, testCase := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate?token="+token, &api.MigrateRepoOptions{ CloneAddr: testCase.cloneURL, RepoOwnerID: testCase.userID, @@ -371,7 +371,7 @@ func TestAPIRepoMigrateConflict(t *testing.T) { func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { username := "user2" - baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeRepo) + baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) u.Path = baseAPITestContext.GitPath() @@ -406,7 +406,7 @@ func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) var repo api.Repository req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1") @@ -438,7 +438,7 @@ func TestAPIOrgRepoCreate(t *testing.T) { for _, testCase := range testCases { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos?token="+token, testCase.orgName), &api.CreateRepoOption{ Name: testCase.repoName, }) @@ -452,7 +452,7 @@ func TestAPIRepoCreateConflict(t *testing.T) { func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { username := "user2" - baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeRepo) + baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) u.Path = baseAPITestContext.GitPath() @@ -502,7 +502,7 @@ func TestAPIRepoTransfer(t *testing.T) { // create repo to move user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) repoName := "moveME" apiRepo := new(api.Repository) req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{ @@ -520,7 +520,7 @@ func TestAPIRepoTransfer(t *testing.T) { user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID}) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) session = loginUser(t, user.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer?token=%s", repo.OwnerName, repo.Name, token), &api.TransferRepoOption{ NewOwner: testCase.newOwner, TeamIDs: testCase.teams, @@ -537,7 +537,7 @@ func transfer(t *testing.T) *repo_model.Repository { // create repo to move user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) repoName := "moveME" apiRepo := new(api.Repository) req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/repos?token=%s", token), &api.CreateRepoOption{ @@ -567,7 +567,7 @@ func TestAPIAcceptTransfer(t *testing.T) { // try to accept with not authorized user session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) MakeRequest(t, req, http.StatusForbidden) @@ -577,7 +577,7 @@ func TestAPIAcceptTransfer(t *testing.T) { // accept transfer session = loginUser(t, "user4") - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept?token=%s", repo.OwnerName, repo.Name, token)) resp := MakeRequest(t, req, http.StatusAccepted) @@ -593,7 +593,7 @@ func TestAPIRejectTransfer(t *testing.T) { // try to reject with not authorized user session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) MakeRequest(t, req, http.StatusForbidden) @@ -603,7 +603,7 @@ func TestAPIRejectTransfer(t *testing.T) { // reject transfer session = loginUser(t, "user4") - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject?token=%s", repo.OwnerName, repo.Name, token)) resp := MakeRequest(t, req, http.StatusOK) @@ -617,7 +617,7 @@ func TestAPIGenerateRepo(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) @@ -653,7 +653,7 @@ func TestAPIRepoGetReviewers(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers?token=%s", user.Name, repo.Name, token) @@ -667,7 +667,7 @@ func TestAPIRepoGetAssignees(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees?token=%s", user.Name, repo.Name, token) diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go index ab9fd9bb96..251b9a3b65 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -60,7 +60,7 @@ func TestAPIRepoTopic(t *testing.T) { repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // Get user2's token - token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeRepo) + token2 := getUserToken(t, user2.Name, auth_model.AccessTokenScopeWriteRepository) // Test read topics using login url := fmt.Sprintf("/api/v1/repos/%s/%s/topics", user2.Name, repo2.Name) @@ -140,7 +140,7 @@ func TestAPIRepoTopic(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) // Get user4's token - token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeRepo) + token4 := getUserToken(t, user4.Name, auth_model.AccessTokenScopeWriteRepository) // Test read topics with write access url = fmt.Sprintf("/api/v1/repos/%s/%s/topics?token=%s", user3.Name, repo3.Name, token4) diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index 60c61394d4..a9ae890717 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -33,7 +33,7 @@ func TestAPITeam(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) req := NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamUser.TeamID) resp := MakeRequest(t, req, http.StatusOK) @@ -48,7 +48,7 @@ func TestAPITeam(t *testing.T) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) session = loginUser(t, user2.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamUser.TeamID) _ = MakeRequest(t, req, http.StatusForbidden) @@ -58,7 +58,7 @@ func TestAPITeam(t *testing.T) { // Get an admin user able to create, update and delete teams. user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session = loginUser(t, user.Name) - token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeAdminOrg) + token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) @@ -262,7 +262,7 @@ func TestAPITeamSearch(t *testing.T) { var results TeamSearchResults - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrg) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) req := NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "_team", token) resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &results) @@ -272,7 +272,7 @@ func TestAPITeamSearch(t *testing.T) { // no access if not organization member user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrg) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s&token=%s", org.Name, "team", token5) MakeRequest(t, req, http.StatusForbidden) @@ -287,7 +287,7 @@ func TestAPIGetTeamRepo(t *testing.T) { var results api.Repository - token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrg) + token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadOrganization) req := NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token) resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &results) @@ -295,7 +295,7 @@ func TestAPIGetTeamRepo(t *testing.T) { // no access if not organization member user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) - token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrg) + token5 := getUserToken(t, user5.Name, auth_model.AccessTokenScopeReadOrganization) req = NewRequestf(t, "GET", "/api/v1/teams/%d/repos/%s/?token=%s", team.ID, teamRepo.FullName(), token5) MakeRequest(t, req, http.StatusNotFound) diff --git a/tests/integration/api_team_user_test.go b/tests/integration/api_team_user_test.go index 468697a393..aa33c69041 100644 --- a/tests/integration/api_team_user_test.go +++ b/tests/integration/api_team_user_test.go @@ -24,7 +24,7 @@ func TestAPITeamUser(t *testing.T) { normalUsername := "user2" session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1?token="+token) MakeRequest(t, req, http.StatusNotFound) diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 498bd41b0f..0eecccb653 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -4,14 +4,18 @@ package integration import ( + "fmt" "net/http" "testing" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" ) // TestAPICreateAndDeleteToken tests that token that was just created can be deleted @@ -19,9 +23,518 @@ func TestAPICreateAndDeleteToken(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ - "name": "test-key-1", + newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil) + deleteAPIAccessToken(t, newAccessToken, user) + + newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil) + deleteAPIAccessToken(t, newAccessToken, user) +} + +// TestAPIDeleteMissingToken ensures that error is thrown when token not found +func TestAPIDeleteMissingToken(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) +} + +type permission struct { + category auth_model.AccessTokenScopeCategory + level auth_model.AccessTokenScopeLevel +} + +type requiredScopeTestCase struct { + url string + method string + requiredPermissions []permission +} + +func (c *requiredScopeTestCase) Name() string { + return fmt.Sprintf("%v %v", c.method, c.url) +} + +// TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access +// when the correct token scope is not included. +func TestAPIDeniesPermissionBasedOnTokenScope(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // We'll assert that each endpoint, when fetched with a token with all + // scopes *except* the ones specified, a forbidden status code is returned. + // + // This is to protect against endpoints having their access check copied + // from other endpoints and not updated. + // + // Test cases are in alphabetical order by URL. + // + // Note: query parameters are not currently supported since the token is + // appended with `?=token=<token>`. + testCases := []requiredScopeTestCase{ + { + "/api/v1/admin/emails", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Read, + }, + }, + }, + { + "/api/v1/admin/users", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Read, + }, + }, + }, + { + "/api/v1/admin/users", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Write, + }, + }, + }, + { + "/api/v1/admin/users/user2", + "PATCH", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Write, + }, + }, + }, + { + "/api/v1/admin/users/user2/orgs", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Read, + }, + }, + }, + { + "/api/v1/admin/users/user2/orgs", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Write, + }, + }, + }, + { + "/api/v1/admin/orgs", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryAdmin, + auth_model.Read, + }, + }, + }, + { + "/api/v1/markdown", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryMisc, + auth_model.Write, + }, + }, + }, + { + "/api/v1/markdown/raw", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryMisc, + auth_model.Write, + }, + }, + }, + { + "/api/v1/notifications", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryNotification, + auth_model.Read, + }, + }, + }, + { + "/api/v1/notifications", + "PUT", + []permission{ + { + auth_model.AccessTokenScopeCategoryNotification, + auth_model.Write, + }, + }, + }, + { + "/api/v1/org/org1/repos", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryOrganization, + auth_model.Write, + }, + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Write, + }, + }, + }, + { + "/api/v1/packages/user1/type/name/1", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryPackage, + auth_model.Read, + }, + }, + }, + { + "/api/v1/packages/user1/type/name/1", + "DELETE", + []permission{ + { + auth_model.AccessTokenScopeCategoryPackage, + auth_model.Write, + }, + }, + }, + { + "/api/v1/repos/user1/repo1", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1", + "PATCH", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Write, + }, + }, + }, + { + "/api/v1/repos/user1/repo1", + "DELETE", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Write, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/branches", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/archive/foo", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/issues", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryIssue, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/media/foo", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/raw/foo", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/teams", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/teams/team1", + "PUT", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Write, + }, + }, + }, + { + "/api/v1/repos/user1/repo1/transfer", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Write, + }, + }, + }, + // Private repo + { + "/api/v1/repos/user2/repo2", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + // Private repo + { + "/api/v1/repos/user2/repo2", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryRepository, + auth_model.Read, + }, + }, + }, + { + "/api/v1/settings/api", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryMisc, + auth_model.Read, + }, + }, + }, + { + "/api/v1/user", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + { + "/api/v1/user/emails", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + { + "/api/v1/user/emails", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Write, + }, + }, + }, + { + "/api/v1/user/emails", + "DELETE", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Write, + }, + }, + }, + { + "/api/v1/user/applications/oauth2", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + { + "/api/v1/user/applications/oauth2", + "POST", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Write, + }, + }, + }, + { + "/api/v1/users/search", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + // Private user + { + "/api/v1/users/user31", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + // Private user + { + "/api/v1/users/user31/gpg_keys", + "GET", + []permission{ + { + auth_model.AccessTokenScopeCategoryUser, + auth_model.Read, + }, + }, + }, + } + + // User needs to be admin so that we can verify that tokens without admin + // scopes correctly deny access. + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + assert.True(t, user.IsAdmin, "User needs to be admin") + + for _, testCase := range testCases { + runTestCase(t, &testCase, user) + } +} + +// runTestCase Helper function to run a single test case. +func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model.User) { + t.Run(testCase.Name(), func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Create a token with all scopes NOT required by the endpoint. + var unauthorizedScopes []auth_model.AccessTokenScope + for _, category := range auth_model.AllAccessTokenScopeCategories { + // For permissions, Write > Read > NoAccess. So we need to + // find the minimum required, and only grant permission up to but + // not including the minimum required. + minRequiredLevel := auth_model.Write + categoryIsRequired := false + for _, requiredPermission := range testCase.requiredPermissions { + if requiredPermission.category != category { + continue + } + categoryIsRequired = true + if requiredPermission.level < minRequiredLevel { + minRequiredLevel = requiredPermission.level + } + } + unauthorizedLevel := auth_model.Write + if categoryIsRequired { + if minRequiredLevel == auth_model.Read { + unauthorizedLevel = auth_model.NoAccess + } else if minRequiredLevel == auth_model.Write { + unauthorizedLevel = auth_model.Read + } else { + assert.Failf(t, "Invalid test case", "Unknown access token scope level: %v", minRequiredLevel) + return + } + } + + if unauthorizedLevel == auth_model.NoAccess { + continue + } + cateogoryUnauthorizedScopes := auth_model.GetRequiredScopes( + unauthorizedLevel, + category) + unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...) + } + + accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes) + defer deleteAPIAccessToken(t, accessToken, user) + + // Add API access token to the URL. + url := fmt.Sprintf("%s?token=%s", testCase.url, accessToken.Token) + + // Request the endpoint. Verify that permission is denied. + req := NewRequestf(t, testCase.method, url) + MakeRequest(t, req, http.StatusForbidden) }) +} + +// createAPIAccessTokenWithoutCleanUp Create an API access token and assert that +// creation succeeded. The caller is responsible for deleting the token. +func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken { + payload := map[string]interface{}{ + "name": tokenName, + } + if scopes != nil { + for _, scope := range *scopes { + scopes, scopesExists := payload["scopes"].([]string) + if !scopesExists { + scopes = make([]string, 0) + } + scopes = append(scopes, string(scope)) + payload["scopes"] = scopes + } + } + log.Debug("Requesting creation of token with scopes: %v", scopes) + req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", payload) + req = AddBasicAuthHeader(req, user.Name) resp := MakeRequest(t, req, http.StatusCreated) @@ -34,32 +547,15 @@ func TestAPICreateAndDeleteToken(t *testing.T) { UID: user.ID, }) - req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID) - req = AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusNoContent) - - unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: newAccessToken.ID}) - - req = NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ - "name": "test-key-2", - }) - req = AddBasicAuthHeader(req, user.Name) - resp = MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, &newAccessToken) + return newAccessToken +} - req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%s", newAccessToken.Name) +// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that +// deletion succeeded. +func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) { + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", accessToken.ID) req = AddBasicAuthHeader(req, user.Name) MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: newAccessToken.ID}) -} - -// TestAPIDeleteMissingToken ensures that error is thrown when token not found -func TestAPIDeleteMissingToken(t *testing.T) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID) - req = AddBasicAuthHeader(req, user.Name) - MakeRequest(t, req, http.StatusNotFound) + unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID}) } diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go index 09083d9ce8..9fff42af42 100644 --- a/tests/integration/api_user_email_test.go +++ b/tests/integration/api_user_email_test.go @@ -46,7 +46,7 @@ func TestAPIAddEmail(t *testing.T) { normalUsername := "user2" session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeUser) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) opts := api.CreateEmailOption{ Emails: []string{"user101@example.com"}, @@ -83,7 +83,7 @@ func TestAPIDeleteEmail(t *testing.T) { normalUsername := "user2" session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeUser) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) opts := api.DeleteEmailOption{ Emails: []string{"user2-3@example.com"}, diff --git a/tests/integration/api_user_follow_test.go b/tests/integration/api_user_follow_test.go index c7ad62e649..62717af90e 100644 --- a/tests/integration/api_user_follow_test.go +++ b/tests/integration/api_user_follow_test.go @@ -22,10 +22,10 @@ func TestAPIFollow(t *testing.T) { user2 := "user1" session1 := loginUser(t, user1) - token1 := getTokenForLoggedInUser(t, session1) + token1 := getTokenForLoggedInUser(t, session1, auth_model.AccessTokenScopeReadUser) session2 := loginUser(t, user2) - token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeUserFollow) + token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser) t.Run("Follow", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_user_heatmap_test.go b/tests/integration/api_user_heatmap_test.go index 432306914c..5a7e58a02d 100644 --- a/tests/integration/api_user_heatmap_test.go +++ b/tests/integration/api_user_heatmap_test.go @@ -10,6 +10,7 @@ import ( "time" activities_model "code.gitea.io/gitea/models/activities" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/tests" @@ -20,7 +21,7 @@ func TestUserHeatmap(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" normalUsername := "user2" - token := getUserToken(t, adminUsername) + token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) fakeNow := time.Date(2011, 10, 20, 0, 0, 0, 0, time.Local) timeutil.Set(fakeNow) diff --git a/tests/integration/api_user_info_test.go b/tests/integration/api_user_info_test.go index 65262792b7..82cd97e904 100644 --- a/tests/integration/api_user_info_test.go +++ b/tests/integration/api_user_info_test.go @@ -8,6 +8,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/tests" @@ -21,7 +22,7 @@ func TestAPIUserInfo(t *testing.T) { user2 := "user31" session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) t.Run("GetInfo", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_user_org_perm_test.go b/tests/integration/api_user_org_perm_test.go index ac575b1f01..40870f39ff 100644 --- a/tests/integration/api_user_org_perm_test.go +++ b/tests/integration/api_user_org_perm_test.go @@ -33,7 +33,7 @@ func sampleTest(t *testing.T, auoptc apiUserOrgPermTestCase) { defer tests.PrepareTestEnv(t)() session := loginUser(t, auoptc.LoginUser) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/orgs/%s/permissions?token=%s", auoptc.User, auoptc.Organization, token)) resp := MakeRequest(t, req, http.StatusOK) @@ -126,7 +126,7 @@ func TestUnknowUser(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/unknow/orgs/org25/permissions?token=%s", token)) resp := MakeRequest(t, req, http.StatusNotFound) @@ -140,7 +140,7 @@ func TestUnknowOrganization(t *testing.T) { defer tests.PrepareTestEnv(t)() session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrg) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadOrganization) req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/user1/orgs/unknow/permissions?token=%s", token)) resp := MakeRequest(t, req, http.StatusNotFound) diff --git a/tests/integration/api_user_orgs_test.go b/tests/integration/api_user_orgs_test.go index 8f914b4875..323facaf48 100644 --- a/tests/integration/api_user_orgs_test.go +++ b/tests/integration/api_user_orgs_test.go @@ -70,7 +70,7 @@ func TestUserOrgs(t *testing.T) { func getUserOrgs(t *testing.T, userDoer, userCheck string) (orgs []*api.Organization) { token := "" if len(userDoer) != 0 { - token = getUserToken(t, userDoer, auth_model.AccessTokenScopeReadOrg) + token = getUserToken(t, userDoer, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) } urlStr := fmt.Sprintf("/api/v1/users/%s/orgs?token=%s", userCheck, token) req := NewRequest(t, "GET", urlStr) @@ -92,7 +92,7 @@ func TestMyOrgs(t *testing.T) { MakeRequest(t, req, http.StatusUnauthorized) normalUsername := "user2" - token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrg) + token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser) req = NewRequest(t, "GET", "/api/v1/user/orgs?token="+token) resp := MakeRequest(t, req, http.StatusOK) var orgs []*api.Organization diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index dc11281c46..be14d5a6b2 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -8,6 +8,7 @@ import ( "net/http" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" @@ -26,7 +27,7 @@ func TestAPIUserSearchLoggedIn(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) query := "user2" req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) resp := MakeRequest(t, req, http.StatusOK) @@ -65,7 +66,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" session := loginUser(t, adminUsername) - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) query := "user31" req := NewRequestf(t, "GET", "/api/v1/users/search?token=%s&q=%s", token, query) req.SetBasicAuth(token, "x-oauth-basic") diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 6a486c19a8..15a555d17d 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -22,13 +22,13 @@ func TestAPIStar(t *testing.T) { repo := "user2/repo1" session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session) - tokenWithRepoScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithUserScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) t.Run("Star", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithRepoScope)) + req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithUserScope)) MakeRequest(t, req, http.StatusNoContent) }) @@ -49,7 +49,7 @@ func TestAPIStar(t *testing.T) { t.Run("GetMyStarredRepos", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred?token=%s", tokenWithRepoScope)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred?token=%s", tokenWithUserScope)) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) @@ -63,17 +63,17 @@ func TestAPIStar(t *testing.T) { t.Run("IsStarring", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithRepoScope)) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithUserScope)) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo+"notexisting", tokenWithRepoScope)) + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo+"notexisting", tokenWithUserScope)) MakeRequest(t, req, http.StatusNotFound) }) t.Run("Unstar", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithRepoScope)) + req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s?token=%s", repo, tokenWithUserScope)) MakeRequest(t, req, http.StatusNoContent) }) } diff --git a/tests/integration/api_user_watch_test.go b/tests/integration/api_user_watch_test.go index 5702962573..c07fd288d1 100644 --- a/tests/integration/api_user_watch_test.go +++ b/tests/integration/api_user_watch_test.go @@ -22,8 +22,8 @@ func TestAPIWatch(t *testing.T) { repo := "user2/repo1" session := loginUser(t, user) - token := getTokenForLoggedInUser(t, session) - tokenWithRepoScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + tokenWithRepoScope := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser) t.Run("Watch", func(t *testing.T) { defer tests.PrintCurrentTest(t)() diff --git a/tests/integration/api_wiki_test.go b/tests/integration/api_wiki_test.go index 3f85074c8a..f598982555 100644 --- a/tests/integration/api_wiki_test.go +++ b/tests/integration/api_wiki_test.go @@ -180,7 +180,7 @@ func TestAPINewWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new?token=%s", username, "repo1", token) @@ -197,7 +197,7 @@ func TestAPIEditWikiPage(t *testing.T) { defer tests.PrepareTestEnv(t)() username := "user2" session := loginUser(t, username) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/Page-With-Spaced-Name?token=%s", username, "repo1", token) diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go index 9ad795d53a..0b6707845b 100644 --- a/tests/integration/dump_restore_test.go +++ b/tests/integration/dump_restore_test.go @@ -51,7 +51,7 @@ func TestDumpRestore(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, repoOwner.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) // // Phase 1: dump repo1 from the Gitea instance to the filesystem diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index cfb2456223..f0022f4db3 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -117,7 +117,7 @@ func TestEmptyRepoAddFileByAPI(t *testing.T) { assert.NoError(t, err) session := loginUser(t, "user30") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) url := fmt.Sprintf("/api/v1/repos/user30/empty/contents/new-file.txt?token=%s", token) req := NewRequestWithJSON(t, "POST", url, &api.CreateFileOptions{ diff --git a/tests/integration/eventsource_test.go b/tests/integration/eventsource_test.go index 4fdb8cd6f5..734f4a6a0a 100644 --- a/tests/integration/eventsource_test.go +++ b/tests/integration/eventsource_test.go @@ -60,7 +60,7 @@ func TestEventSourceManagerRun(t *testing.T) { thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5}) assert.NoError(t, thread5.LoadAttributes(db.DefaultContext)) session := loginUser(t, user2.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeNotification) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteNotification, auth_model.AccessTokenScopeWriteRepository) var apiNL []api.NotificationThread diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index 8d5c0fc390..0c3a8616f0 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -44,11 +44,11 @@ func TestGit(t *testing.T) { func testGit(t *testing.T, u *url.URL) { username := "user2" - baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeWritePublicKey, auth_model.AccessTokenScopeDeleteRepo) + baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) u.Path = baseAPITestContext.GitPath() - forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeRepo) + forkedUserCtx := NewAPITestContext(t, "user4", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("HTTP", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -359,7 +359,7 @@ func doBranchProtectPRMerge(baseCtx *APITestContext, dstPath string) func(t *tes t.Run("CreateBranchProtected", doGitCreateBranch(dstPath, "protected")) t.Run("PushProtectedBranch", doGitPushTestRepository(dstPath, "origin", "protected")) - ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) t.Run("ProtectProtectedBranchNoWhitelist", doProtectBranch(ctx, "protected", "", "")) t.Run("GenerateCommit", func(t *testing.T) { _, err := generateCommitWithNewData(littleSize, dstPath, "user2@example.com", "User Two", "branch-data-file-") @@ -603,7 +603,7 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { return func(t *testing.T) { defer tests.PrintCurrentTest(t)() - ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, baseCtx.Username, baseCtx.Reponame, auth_model.AccessTokenScopeWriteRepository) t.Run("CheckoutProtected", doGitCheckoutBranch(dstPath, "protected")) t.Run("PullProtected", doGitPull(dstPath, "origin", "protected")) diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index 36095694b0..5b49c91c59 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -70,7 +70,7 @@ func TestGPGGit(t *testing.T) { t.Run("Unsigned-Initial", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { assert.NotNil(t, branch.Commit) @@ -94,7 +94,7 @@ func TestGPGGit(t *testing.T) { t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -111,7 +111,7 @@ func TestGPGGit(t *testing.T) { t.Run("Unsigned-Initial-CRUD-Never", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "parentsigned", "parentsigned-never", "unsigned-never2.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -124,7 +124,7 @@ func TestGPGGit(t *testing.T) { t.Run("Unsigned-Initial-CRUD-Always", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { assert.NotNil(t, response.Verification) @@ -161,7 +161,7 @@ func TestGPGGit(t *testing.T) { t.Run("Unsigned-Initial-CRUD-ParentSigned", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile( t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) { assert.NotNil(t, response.Verification) @@ -184,7 +184,7 @@ func TestGPGGit(t *testing.T) { t.Run("AlwaysSign-Initial", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { assert.NotNil(t, branch.Commit) @@ -212,7 +212,7 @@ func TestGPGGit(t *testing.T) { t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-always-never", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { @@ -225,7 +225,7 @@ func TestGPGGit(t *testing.T) { u.Path = baseAPITestContext.GitPath() t.Run("AlwaysSign-Initial-CRUD-ParentSigned-On-Always", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-always-parent", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { @@ -244,7 +244,7 @@ func TestGPGGit(t *testing.T) { t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-always-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { @@ -264,7 +264,7 @@ func TestGPGGit(t *testing.T) { t.Run("UnsignedMerging", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) var err error t.Run("CreatePullRequest", func(t *testing.T) { pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "never2")(t) @@ -285,7 +285,7 @@ func TestGPGGit(t *testing.T) { t.Run("BaseSignedMerging", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) var err error t.Run("CreatePullRequest", func(t *testing.T) { pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "parentsigned2")(t) @@ -306,7 +306,7 @@ func TestGPGGit(t *testing.T) { t.Run("CommitsSignedMerging", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) var err error t.Run("CreatePullRequest", func(t *testing.T) { pr, err = doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, "master", "always-parentsigned")(t) diff --git a/tests/integration/lfs_getobject_test.go b/tests/integration/lfs_getobject_test.go index ba236d355f..fe070c62d5 100644 --- a/tests/integration/lfs_getobject_test.go +++ b/tests/integration/lfs_getobject_test.go @@ -41,13 +41,13 @@ func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string return pointer.Oid } -func storeAndGetLfsToken(t *testing.T, ts auth.AccessTokenScope, content *[]byte, extraHeader *http.Header, expectedStatus int) *httptest.ResponseRecorder { +func storeAndGetLfsToken(t *testing.T, content *[]byte, extraHeader *http.Header, expectedStatus int, ts ...auth.AccessTokenScope) *httptest.ResponseRecorder { repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1") assert.NoError(t, err) oid := storeObjectInRepo(t, repo.ID, content) defer git_model.RemoveLFSMetaObjectByOid(db.DefaultContext, repo.ID, oid) - token := getUserToken(t, "user2", ts) + token := getUserToken(t, "user2", ts...) // Request OID req := NewRequest(t, "GET", "/user2/repo1.git/info/lfs/objects/"+oid+"/test") @@ -119,7 +119,7 @@ func TestGetLFSSmallToken(t *testing.T) { defer tests.PrepareTestEnv(t)() content := []byte("A very small file\n") - resp := storeAndGetLfsToken(t, auth.AccessTokenScopePublicRepo, &content, nil, http.StatusOK) + resp := storeAndGetLfsToken(t, &content, nil, http.StatusOK, auth.AccessTokenScopePublicOnly, auth.AccessTokenScopeReadRepository) checkResponseTestContentEncoding(t, &content, resp, false) } @@ -127,7 +127,7 @@ func TestGetLFSSmallTokenFail(t *testing.T) { defer tests.PrepareTestEnv(t)() content := []byte("A very small file\n") - storeAndGetLfsToken(t, auth.AccessTokenScopeNotification, &content, nil, http.StatusForbidden) + storeAndGetLfsToken(t, &content, nil, http.StatusForbidden, auth.AccessTokenScopeReadNotification) } func TestGetLFSLarge(t *testing.T) { diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index a925493d7c..f25329f66b 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -67,7 +67,7 @@ func TestMigrateGiteaForm(t *testing.T) { repoName := "repo1" repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: ownerName}) session := loginUser(t, ownerName) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) // Step 0: verify the repo is available req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName)) diff --git a/tests/integration/org_count_test.go b/tests/integration/org_count_test.go index 8f850a170f..e46b83169b 100644 --- a/tests/integration/org_count_test.go +++ b/tests/integration/org_count_test.go @@ -25,7 +25,7 @@ func testOrgCounts(t *testing.T, u *url.URL) { orgOwner := "user2" orgName := "testOrg" orgCollaborator := "user4" - ctx := NewAPITestContext(t, orgOwner, "repo1", auth_model.AccessTokenScopeAdminOrg) + ctx := NewAPITestContext(t, orgOwner, "repo1", auth_model.AccessTokenScopeWriteOrganization) var ownerCountRepos map[string]int var collabCountRepos map[string]int diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index bfa6380e8a..64d93e4083 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -159,7 +159,7 @@ func TestOrgRestrictedUser(t *testing.T) { // Therefore create a read-only team adminSession := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeAdminOrg) + token := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeWriteOrganization) teamToCreate := &api.CreateTeamOption{ Name: "codereader", diff --git a/tests/integration/privateactivity_test.go b/tests/integration/privateactivity_test.go index 6e1377ae1f..5d3291bfe3 100644 --- a/tests/integration/privateactivity_test.go +++ b/tests/integration/privateactivity_test.go @@ -34,7 +34,7 @@ func testPrivateActivityDoSomethingForActionEntries(t *testing.T) { owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}) session := loginUser(t, privateActivityTestUser) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues?state=all&token=%s", owner.Name, repoBefore.Name, token) req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateIssueOption{ Body: "test", @@ -125,7 +125,7 @@ func testPrivateActivityHelperHasHeatmapContentFromPublic(t *testing.T) bool { } func testPrivateActivityHelperHasHeatmapContentFromSession(t *testing.T, session *TestSession) bool { - token := getTokenForLoggedInUser(t, session) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) req := NewRequestf(t, "GET", "/api/v1/users/%s/heatmap?token=%s", privateActivityTestUser, token) resp := session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index f6a36f60af..8890347c36 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -218,7 +218,7 @@ func TestCantMergeConflict(t *testing.T) { testEditFileToNewBranch(t, session, "user1", "repo1", "master", "base", "README.md", "Hello, World (Edited Twice)\n") // Use API to create a conflicting pr - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ Head: "conflict", Base: "base", @@ -326,7 +326,7 @@ func TestCantMergeUnrelated(t *testing.T) { testEditFileToNewBranch(t, session, "user1", "repo1", "master", "conflict", "README.md", "Hello, World (Edited Once)\n") // Use API to create a conflicting pr - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls?token=%s", "user1", "repo1", token), &api.CreatePullRequestOption{ Head: "unrelated", Base: "base", diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index 736d1ee4f0..0bdb80ecbf 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -64,7 +64,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { api.CommitStatusWarning: "gitea-exclamation", } - testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeRepo) + testCtx := NewAPITestContext(t, "user1", "repo1", auth_model.AccessTokenScopeWriteRepository) // Update commit status, and check if icon is updated as well for _, status := range statusList { diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index b94731002f..fa56cec485 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -39,7 +39,7 @@ func TestAPIPullUpdate(t *testing.T) { assert.NoError(t, pr.LoadIssue(db.DefaultContext)) session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index) session.MakeRequest(t, req, http.StatusOK) @@ -67,7 +67,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) { assert.NoError(t, pr.LoadIssue(db.DefaultContext)) session := loginUser(t, "user2") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestf(t, "POST", "/api/v1/repos/%s/%s/pulls/%d/update?style=rebase&token="+token, pr.BaseRepo.OwnerName, pr.BaseRepo.Name, pr.Issue.Index) session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 0cfd21485d..99927f1929 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -52,7 +52,7 @@ func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { assert.NotEmpty(t, commitURL) // Call API to add status for commit - ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ State: api.CommitStatusState(state), TargetURL: "http://test.ci/", @@ -157,7 +157,7 @@ func TestRepoCommitsStatusParallel(t *testing.T) { wg.Add(1) go func(parentT *testing.T, i int) { parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { - ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeRepoStatus) + ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) runBody := doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ State: api.CommitStatusPending, TargetURL: "http://test.ci/", @@ -188,7 +188,7 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { assert.NotEmpty(t, commitURL) // Call API to add status for commit - ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeRepo) + ctx := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository) t.Run("CreateStatus", doAPICreateCommitStatus(ctx, path.Base(commitURL), api.CreateStatusOption{ State: api.CommitStatusSuccess, TargetURL: "http://test.ci/", diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index 1e9dc264a6..eb3a3e926a 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -48,8 +48,8 @@ func TestPushDeployKeyOnEmptyRepo(t *testing.T) { func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { // OK login - ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeRepo) - ctxWithDeleteRepo := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeDeleteRepo) + ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + ctxWithDeleteRepo := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) keyname := fmt.Sprintf("%s-push", ctx.Reponame) u.Path = ctx.GitPath() @@ -92,8 +92,8 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { keyname := fmt.Sprintf("%s-push", reponame) // OK login - ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeAdminPublicKey) - ctxWithDeleteRepo := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeRepo, auth_model.AccessTokenScopeAdminPublicKey, auth_model.AccessTokenScopeDeleteRepo) + ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + ctxWithDeleteRepo := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) otherCtx := ctx otherCtx.Reponame = "ssh-key-test-repo-2" diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index 65cba1dee3..3e4d967686 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -166,7 +166,7 @@ Note: This user hasn't uploaded any GPG keys. // Import key // User1 <user1@example.com> session := loginUser(t, "user1") - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteGPGKey) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) testCreateGPGKey(t, session.MakeRequest, token, http.StatusCreated, `-----BEGIN PGP PUBLIC KEY BLOCK----- mQENBFyy/VUBCADJ7zbM20Z1RWmFoVgp5WkQfI2rU1Vj9cQHes9i42wVLLtcbPeo diff --git a/web_src/css/helpers.css b/web_src/css/helpers.css index cfd2151100..14ef64b252 100644 --- a/web_src/css/helpers.css +++ b/web_src/css/helpers.css @@ -64,6 +64,7 @@ Gitea's private styles use `g-` prefix. .gt-break-all { word-break: break-all !important; } .gt-content-center { align-content: center !important; } .gt-cursor-default { cursor: default !important; } +.gt-cursor-pointer { cursor: pointer !important; } .gt-invisible { visibility: hidden !important; } .gt-items-start { align-items: flex-start !important; } .gt-pointer-events-none { pointer-events: none !important; } diff --git a/web_src/js/components/ScopedAccessTokenSelector.vue b/web_src/js/components/ScopedAccessTokenSelector.vue new file mode 100644 index 0000000000..769f3262b4 --- /dev/null +++ b/web_src/js/components/ScopedAccessTokenSelector.vue @@ -0,0 +1,138 @@ +<template> + <div class="scoped-access-token-category"> + <div class="field gt-pl-2"> + <label class="checkbox-label"> + <input + ref="category" + v-model="categorySelected" + class="scope-checkbox scoped-access-token-input" + type="checkbox" + name="scope" + :value="'write:' + category" + @input="onCategoryInput" + > + {{ category }} + </label> + </div> + <div class="field gt-pl-4"> + <div class="inline field"> + <label class="checkbox-label"> + <input + ref="read" + v-model="readSelected" + :disabled="disableIndividual || writeSelected" + class="scope-checkbox scoped-access-token-input" + type="checkbox" + name="scope" + :value="'read:' + category" + @input="onIndividualInput" + > + read:{{ category }} + </label> + </div> + <div class="inline field"> + <label class="checkbox-label"> + <input + ref="write" + v-model="writeSelected" + :disabled="disableIndividual" + class="scope-checkbox scoped-access-token-input" + type="checkbox" + name="scope" + :value="'write:' + category" + @input="onIndividualInput" + > + write:{{ category }} + </label> + </div> + </div> + </div> +</template> + +<script> +import {createApp} from 'vue'; +import {showElem} from '../utils/dom.js'; + +const sfc = { + props: { + category: { + type: String, + required: true, + }, + }, + + data: () => ({ + categorySelected: false, + disableIndividual: false, + readSelected: false, + writeSelected: false, + }), + + methods: { + /** + * When entire category is toggled + * @param {Event} e + */ + onCategoryInput(e) { + e.preventDefault(); + this.disableIndividual = this.$refs.category.checked; + this.writeSelected = this.$refs.category.checked; + this.readSelected = this.$refs.category.checked; + }, + + /** + * When an individual level of category is toggled + * @param {Event} e + */ + onIndividualInput(e) { + e.preventDefault(); + if (this.$refs.write.checked) { + this.readSelected = true; + } + this.categorySelected = this.$refs.write.checked; + }, + } +}; + +export default sfc; + +/** + * Initialize category toggle sections + */ +export function initScopedAccessTokenCategories() { + for (const el of document.getElementsByTagName('scoped-access-token-category')) { + const category = el.getAttribute('category'); + createApp(sfc, { + category, + }).mount(el); + } + + document.getElementById('scoped-access-submit')?.addEventListener('click', (e) => { + e.preventDefault(); + // check that at least one scope has been selected + for (const el of document.getElementsByClassName('scoped-access-token-input')) { + if (el.checked) { + document.getElementById('scoped-access-form').submit(); + } + } + // no scopes selected, show validation error + showElem(document.getElementById('scoped-access-warning')); + }); +} + +</script> + +<style scoped> +.scoped-access-token-category { + padding-top: 10px; + padding-bottom: 10px; +} + +.checkbox-label { + cursor: pointer; +} + +.scope-checkbox { + margin: 4px 5px 0 0; +} +</style> diff --git a/web_src/js/index.js b/web_src/js/index.js index 3d8a7fc325..0c786f96fb 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -2,6 +2,7 @@ import './bootstrap.js'; import {initRepoActivityTopAuthorsChart} from './components/RepoActivityTopAuthors.vue'; +import {initScopedAccessTokenCategories} from './components/ScopedAccessTokenSelector.vue'; import {initDashboardRepoList} from './components/DashboardRepoList.vue'; import {initGlobalCopyToClipboardListener} from './features/clipboard.js'; @@ -177,4 +178,5 @@ onDomReady(() => { initUserSettings(); initRepoDiffView(); initPdfViewer(); + initScopedAccessTokenCategories(); }); |