mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 07:57:32 +00:00
feat(API): support V2 token and session token usage (#6180)
This PR adds support for userinfo and introspection of V2 tokens. Further V2 access tokens and session tokens can be used for authentication on the ZITADEL API (like the current access tokens).
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/zitadel/logging"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/actions/object"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
api_http "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
@@ -119,18 +121,26 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr
|
||||
func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo *oidc.UserInfo, tokenID, subject, origin string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if strings.HasPrefix(tokenID, command.IDPrefixV2) {
|
||||
token, err := o.query.ActiveAccessTokenByToken(ctx, tokenID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = o.isOriginAllowed(ctx, token.ClientID, origin); err != nil {
|
||||
return err
|
||||
}
|
||||
return o.setUserinfo(ctx, userInfo, token.UserID, token.ClientID, token.Scope, nil)
|
||||
}
|
||||
|
||||
token, err := o.repo.TokenByIDs(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
if token.ApplicationID != "" {
|
||||
app, err := o.query.AppByOIDCClientID(ctx, token.ApplicationID, false)
|
||||
if err != nil {
|
||||
if err = o.isOriginAllowed(ctx, token.ApplicationID, origin); err != nil {
|
||||
return err
|
||||
}
|
||||
if origin != "" && !api_http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
}
|
||||
return o.setUserinfo(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes, nil)
|
||||
}
|
||||
@@ -154,6 +164,24 @@ func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo *oidc.Us
|
||||
}
|
||||
|
||||
func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection *oidc.IntrospectionResponse, tokenID, subject, clientID string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if strings.HasPrefix(tokenID, command.IDPrefixV2) {
|
||||
token, err := o.query.ActiveAccessTokenByToken(ctx, tokenID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := o.query.ProjectIDFromClientID(ctx, clientID, false)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
return o.introspect(ctx, introspection,
|
||||
tokenID, token.UserID, token.ClientID, projectID,
|
||||
token.Audience, token.Scope,
|
||||
token.AccessTokenCreation, token.AccessTokenExpiration)
|
||||
}
|
||||
|
||||
token, err := o.repo.TokenByIDs(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
@@ -168,27 +196,10 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
||||
return errors.ThrowPreconditionFailed(err, "OIDC-AGefw", "Errors.Internal")
|
||||
}
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if aud == clientID || aud == projectID {
|
||||
userInfo := new(oidc.UserInfo)
|
||||
err := o.setUserinfo(ctx, userInfo, subject, clientID, token.Scopes, []string{projectID}) // always
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
introspection.SetUserInfo(userInfo)
|
||||
introspection.Scope = token.Scopes
|
||||
introspection.ClientID = token.ApplicationID
|
||||
introspection.TokenType = oidc.BearerToken
|
||||
introspection.Expiration = oidc.FromTime(token.Expiration)
|
||||
introspection.IssuedAt = oidc.FromTime(token.CreationDate)
|
||||
introspection.NotBefore = oidc.FromTime(token.CreationDate)
|
||||
introspection.Audience = token.Audience
|
||||
introspection.Issuer = op.IssuerFromContext(ctx)
|
||||
introspection.JWTID = token.ID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
return o.introspect(ctx, introspection,
|
||||
token.ID, token.UserID, token.ApplicationID, projectID,
|
||||
token.Audience, token.Scopes,
|
||||
token.CreationDate, token.Expiration)
|
||||
}
|
||||
|
||||
func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scope []string) (op.TokenRequest, error) {
|
||||
@@ -230,6 +241,55 @@ func (o *OPStorage) ClientCredentials(ctx context.Context, clientID, clientSecre
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isOriginAllowed checks whether a call by the client to the endpoint is allowed from the provided origin
|
||||
// if no origin is provided, no error will be returned
|
||||
func (o *OPStorage) isOriginAllowed(ctx context.Context, clientID, origin string) error {
|
||||
if origin == "" {
|
||||
return nil
|
||||
}
|
||||
app, err := o.query.AppByOIDCClientID(ctx, clientID, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if api_http.IsOriginAllowed(app.OIDCConfig.AllowedOrigins, origin) {
|
||||
return nil
|
||||
}
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
|
||||
func (o *OPStorage) introspect(
|
||||
ctx context.Context,
|
||||
introspection *oidc.IntrospectionResponse,
|
||||
tokenID, subject, clientID, projectID string,
|
||||
audience, scope []string,
|
||||
tokenCreation, tokenExpiration time.Time,
|
||||
) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
for _, aud := range audience {
|
||||
if aud == clientID || aud == projectID {
|
||||
userInfo := new(oidc.UserInfo)
|
||||
err = o.setUserinfo(ctx, userInfo, subject, clientID, scope, []string{projectID}) // always
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
introspection.SetUserInfo(userInfo)
|
||||
introspection.Scope = scope
|
||||
introspection.ClientID = clientID
|
||||
introspection.TokenType = oidc.BearerToken
|
||||
introspection.Expiration = oidc.FromTime(tokenExpiration)
|
||||
introspection.IssuedAt = oidc.FromTime(tokenCreation)
|
||||
introspection.NotBefore = oidc.FromTime(tokenCreation)
|
||||
introspection.Audience = audience
|
||||
introspection.Issuer = op.IssuerFromContext(ctx)
|
||||
introspection.JWTID = tokenID
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
}
|
||||
|
||||
func (o *OPStorage) checkOrgScopes(ctx context.Context, user *query.User, scopes []string) ([]string, error) {
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
scope := scopes[i]
|
||||
|
Reference in New Issue
Block a user