mirror of https://github.com/go-gitea/gitea.git
oauth2 access token granular scope
This commit is contained in:
parent
32456b6f31
commit
fb20f58795
|
@ -104,7 +104,15 @@ func InfoOAuth(ctx *context.Context) {
|
|||
Picture: ctx.Doer.AvatarLink(ctx),
|
||||
}
|
||||
|
||||
groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer)
|
||||
var accessTokenScope auth.AccessTokenScope
|
||||
if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" {
|
||||
auths := strings.Fields(auHead)
|
||||
if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") {
|
||||
accessTokenScope, _ = auth_service.GetOAuthAccessTokenScopeAndUserID(ctx, auths[1])
|
||||
}
|
||||
}
|
||||
onlyPublicGroups, _ := accessTokenScope.PublicOnly()
|
||||
groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups)
|
||||
if err != nil {
|
||||
ctx.ServerError("Oauth groups for user", err)
|
||||
return
|
||||
|
@ -304,6 +312,9 @@ func AuthorizeOAuth(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
// check if additional scopes
|
||||
ctx.Data["AdditionalScopes"] = oauth2_provider.GrantAdditionalScopes(form.Scope) != auth.AccessTokenScopeAll
|
||||
|
||||
// show authorize page to grant access
|
||||
ctx.Data["Application"] = app
|
||||
ctx.Data["RedirectURI"] = form.RedirectURI
|
||||
|
|
|
@ -77,8 +77,8 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
|
|||
log.Trace("Basic Authorization: Attempting login with username as token")
|
||||
}
|
||||
|
||||
// check oauth2 token
|
||||
uid := CheckOAuthAccessToken(req.Context(), authToken)
|
||||
// get oauth2 token's user's ID
|
||||
_, uid := GetOAuthAccessTokenScopeAndUserID(req.Context(), authToken)
|
||||
if uid != 0 {
|
||||
log.Trace("Basic Authorization: Valid OAuthAccessToken for user[%d]", uid)
|
||||
|
||||
|
|
|
@ -25,33 +25,35 @@ var (
|
|||
_ Method = &OAuth2{}
|
||||
)
|
||||
|
||||
// CheckOAuthAccessToken returns uid of user from oauth token
|
||||
func CheckOAuthAccessToken(ctx context.Context, accessToken string) int64 {
|
||||
// GetOAuthAccessTokenScopeAndUserID returns access token scope and user id
|
||||
func GetOAuthAccessTokenScopeAndUserID(ctx context.Context, accessToken string) (auth_model.AccessTokenScope, int64) {
|
||||
var accessTokenScope auth_model.AccessTokenScope
|
||||
if !setting.OAuth2.Enabled {
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
|
||||
// JWT tokens require a ".", if the token isn't like that, return early
|
||||
if !strings.Contains(accessToken, ".") {
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
|
||||
token, err := oauth2_provider.ParseToken(accessToken, oauth2_provider.DefaultSigningKey)
|
||||
if err != nil {
|
||||
log.Trace("oauth2.ParseToken: %v", err)
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
var grant *auth_model.OAuth2Grant
|
||||
if grant, err = auth_model.GetOAuth2GrantByID(ctx, token.GrantID); err != nil || grant == nil {
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
if token.Kind != oauth2_provider.KindAccessToken {
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
if token.ExpiresAt.Before(time.Now()) || token.IssuedAt.After(time.Now()) {
|
||||
return 0
|
||||
return accessTokenScope, 0
|
||||
}
|
||||
return grant.UserID
|
||||
accessTokenScope = oauth2_provider.GrantAdditionalScopes(grant.Scope)
|
||||
return accessTokenScope, grant.UserID
|
||||
}
|
||||
|
||||
// OAuth2 implements the Auth interface and authenticates requests
|
||||
|
@ -97,10 +99,11 @@ func parseToken(req *http.Request) (string, bool) {
|
|||
func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store DataStore) int64 {
|
||||
// Let's see if token is valid.
|
||||
if strings.Contains(tokenSHA, ".") {
|
||||
uid := CheckOAuthAccessToken(ctx, tokenSHA)
|
||||
accessTokenScope, uid := GetOAuthAccessTokenScopeAndUserID(ctx, tokenSHA)
|
||||
|
||||
if uid != 0 {
|
||||
store.GetData()["IsApiToken"] = true
|
||||
store.GetData()["ApiTokenScope"] = auth_model.AccessTokenScopeAll // fallback to all
|
||||
store.GetData()["ApiTokenScope"] = accessTokenScope
|
||||
}
|
||||
return uid
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ package oauth2_provider //nolint
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
auth "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -69,6 +71,30 @@ type AccessTokenResponse struct {
|
|||
IDToken string `json:"id_token,omitempty"`
|
||||
}
|
||||
|
||||
// GrantAdditionalScopes returns valid scopes coming from grant
|
||||
func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
|
||||
// scopes_supported from templates/user/auth/oidc_wellknown.tmpl
|
||||
scopesSupported := []string{
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"groups",
|
||||
}
|
||||
|
||||
var tokenScopes []string
|
||||
for _, tokenScope := range strings.Split(grantScopes, " ") {
|
||||
if slices.Index(scopesSupported, tokenScope) == -1 {
|
||||
tokenScopes = append(tokenScopes, tokenScope)
|
||||
}
|
||||
}
|
||||
|
||||
accessTokenScope := auth.AccessTokenScope(strings.Join(tokenScopes, ","))
|
||||
if accessTokenWithAdditionalScopes, err := accessTokenScope.Normalize(); err == nil && len(tokenScopes) > 0 {
|
||||
return accessTokenWithAdditionalScopes
|
||||
}
|
||||
return auth.AccessTokenScopeAll
|
||||
}
|
||||
|
||||
func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, serverKey, clientKey JWTSigningKey) (*AccessTokenResponse, *AccessTokenError) {
|
||||
if setting.OAuth2.InvalidateRefreshTokens {
|
||||
if err := grant.IncreaseCounter(ctx); err != nil {
|
||||
|
@ -161,7 +187,10 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
|
|||
idToken.EmailVerified = user.IsActive
|
||||
}
|
||||
if grant.ScopeContains("groups") {
|
||||
groups, err := GetOAuthGroupsForUser(ctx, user)
|
||||
accessTokenScope := GrantAdditionalScopes(grant.Scope)
|
||||
onlyPublicGroups, _ := accessTokenScope.PublicOnly()
|
||||
|
||||
groups, err := GetOAuthGroupsForUser(ctx, user, onlyPublicGroups)
|
||||
if err != nil {
|
||||
log.Error("Error getting groups: %v", err)
|
||||
return nil, &AccessTokenError{
|
||||
|
@ -192,10 +221,10 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
|
|||
|
||||
// returns a list of "org" and "org:team" strings,
|
||||
// that the given user is a part of.
|
||||
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User) ([]string, error) {
|
||||
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) {
|
||||
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
|
||||
UserID: user.ID,
|
||||
IncludePrivate: true,
|
||||
IncludePrivate: !onlyPublicGroups,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserOrgList: %w", err)
|
||||
|
|
|
@ -8,8 +8,11 @@
|
|||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<p>
|
||||
{{if not .AdditionalScopes}}
|
||||
<b>{{ctx.Locale.Tr "auth.authorize_application_description"}}</b><br>
|
||||
{{end}}
|
||||
{{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML}}
|
||||
<p>{{ctx.Locale.Tr "auth.authorize_application_with_scopes" .Scope}}</p>
|
||||
</p>
|
||||
</div>
|
||||
<div class="ui attached segment">
|
||||
|
|
Loading…
Reference in New Issue