Enforce public-only scope for personal access token and OAuth2 additional scopes
#33
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request is combination of two pull requests from Forgejo "compressed" into two commits.
Don't show private repositories and organizations if called via access token with public-only scope
During testing of the "public-only" scope for API tokens, I discovered that the initial implementation didn't guard private repositories and organizations as intended.
I noticed that private repositories and organizations were still visible when using a token with the
public-onlyscope.This PR addresses this issue and ensures that the public-only scope functions correctly.
During my attempts to fix this I realized that the logic was not propagating the
public-onlyflag deep enough. I think that the initial check for thepublic-onlyscope was happening too early in the request lifecycle as if it can be resolved per request where the actual logic should be implemented at the moment when the particular repository or organization is being traversed (so one could check if it is actually private).The implementation in this PR introduces the
publicOnlyTokenfunction to check if access token carries thepublic-onlyscope. It then updates related functions where the traversal of repos and orgs are done.Related functions are:
routers/api/v1/api.gocalled via/api/v1/repos/{username}/{reponame}routers/api/v1/org/org.gocalled via/api/v1/user/orgs,/users/{username}/orgsand/admin/users/{username}/orgsrouters/api/v1/org/org.gocalled via/api/v1/orgsrouters/api/v1/org/org.gocalled via/api/v1/orgs/{org}routers/api/v1/repo/repo.gocalled via/api/v1/repos/searchrouters/api/v1/repo/repo.gocalled via/api/v1/repositories/{id}routers/api/v1/user/repo.gocalled via/api/v1/users/{username}/reposrouters/api/v1/user/repo.gocalled via/api/v1/users/reposrouters/api/v1/user/repo.gocalled via/api/v1/orgs/{org}/reposMiddleware tokenRequiresScopes takes care of required scopes but also set if
public-onlyscope comes with the request. There were few routes which missed the required scopes or did the wrong order with the middleware coming after the call to function returning the results.Tests added:
TestAPIPublicOnlyTokenReposverifies that tokens with public-only scope only show public repositoriesTestAPIPublicOnlyTokenOrgsensures that tokens with public-only scope only display public organizationsEnhancing Gitea OAuth2 Provider with Granular Scopes for Resource Access
This PR was initiated following my personal research to find the lightest possible Single Sign-On solution for self-hosted setups. The existing solutions often seemed too enterprise-oriented, involving many moving parts and services, demanding significant resources while promising planetary-scale capabilities. Others were adequate in supporting basic OAuth2 flows but lacked proper user management features, such as a change password UI.
Forgejo hits the sweet spot for me, provided it supports more granular access permissions for resources under users who accept the OAuth2 application.
This PR aims to introduce this capability as nonintrusively and simply as possible. It acknowledges that the default scope is
alland begins to process only additional scopes typical for OIDC/OAuth2: openid, profile, email, and groups. These then should be the ones already established with personal tokens.Personal tokens define scopes around specific resources: user info, repositories, issues, packages, organizations, notifications, miscellaneous, admin, and activitypub, with access delineated by read and/or write permissions.
The initial case I wanted to address was to have Forgejo act as an OAuth2 Identity Provider. To achieve that, with this PR, I would only add
read:userto provide basic user information. The rest of the scopes should be provided as it is now, combining the existing Forgejo OAuth2 scopes: openid, profile, email, and groups.Another example: if a third party wanted to interact solely with Issues, it would need to add
read:user(for authorization) andread:issue/write:issueto manage Issues.Another scenario could involve adding
read:userandpublic-only, preventing the third party from accessing anything the user has kept private.My approach is based on my understanding of how scopes can be utilized, supported by examples like Sample Use Cases: Scopes and Claims on auth0.com.
I added an additional check to
CheckOAuthAccessTokenthat returns extragrant scopes, which are then added tostore.GetData()["ApiTokenScope"]inuserIDFromTokento be checked against the accepted/stored grant scopes. The main difference is the opportunity to reduce the permissions fromall, as is currently the case, to what is provided by the additional scopes described above.The intent of this PR is to ensure that if nothing is added, or if scopes are added to
openid, profile, email, groupsbut not inAccessTokenScope, everything continues to work as it currently does.