mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-07 07:16:54 +00:00
feat: token introspection, api clients and auth method private_key_jwt (#1276)
* introspect * testingapplication key * date * client keys * fix client keys * fix client keys * access tokens only for users * AuthMethodPrivateKeyJWT * client keys * set introspection info correctly * managae apis * update oidc pkg * cleanup * merge msater * set current sequence in migration * set current sequence in migration * set current sequence in migration * Apply suggestions from code review Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * DeleteAuthNKeysByObjectID * ensure authn keys uptodate * update oidc version * merge master * merge master Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/auth_request/model"
|
||||
authreq_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
@@ -55,13 +56,17 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) {
|
||||
return o.GetKeyByIDAndIssuer(ctx, keyID, userID)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeyByIDAndIssuer(ctx context.Context, keyID, issuer string) (_ *jose.JSONWebKey, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
key, err := o.repo.MachineKeyByID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.UserID != userID {
|
||||
if key.AuthIdentifier != issuer {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-24jm3", "key from different user")
|
||||
}
|
||||
publicKey, err := crypto.BytesToPublicKey(key.PublicKey)
|
||||
@@ -75,6 +80,29 @@ func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID strin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) ValidateJWTProfileScopes(ctx context.Context, subject string, scopes oidc.Scopes) (oidc.Scopes, error) {
|
||||
user, err := o.repo.UserByID(ctx, subject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := len(scopes) - 1; i >= 0; i-- {
|
||||
scope := scopes[i]
|
||||
if strings.HasPrefix(scope, authreq_model.OrgDomainPrimaryScope) {
|
||||
var orgID string
|
||||
org, err := o.repo.OrgByPrimaryDomain(strings.TrimPrefix(scope, authreq_model.OrgDomainPrimaryScope))
|
||||
if err == nil {
|
||||
orgID = org.ID
|
||||
}
|
||||
if orgID != user.ResourceOwner {
|
||||
scopes[i] = scopes[len(scopes)-1]
|
||||
scopes[len(scopes)-1] = ""
|
||||
scopes = scopes[:len(scopes)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopes, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -85,33 +113,32 @@ func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secr
|
||||
return o.repo.AuthorizeOIDCApplication(ctx, id, secret)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, subject, origin string) (_ oidc.UserInfo, err error) {
|
||||
func (o *OPStorage) SetUserinfoFromToken(ctx context.Context, userInfo oidc.UserInfoSetter, tokenID, subject, origin string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
token, err := o.repo.TokenByID(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
if token.ApplicationID != "" {
|
||||
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
}
|
||||
return o.GetUserinfoFromScopes(ctx, token.UserID, token.ApplicationID, token.Scopes)
|
||||
return o.SetUserinfoFromScopes(ctx, userInfo, token.UserID, token.ApplicationID, token.Scopes)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicationID string, scopes []string) (_ oidc.UserInfo, err error) {
|
||||
func (o *OPStorage) SetUserinfoFromScopes(ctx context.Context, userInfo oidc.UserInfoSetter, userID, applicationID string, scopes []string) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
user, err := o.repo.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
userInfo := oidc.NewUserInfo()
|
||||
roles := make([]string, 0)
|
||||
for _, scope := range scopes {
|
||||
switch scope {
|
||||
@@ -160,17 +187,40 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID, applicati
|
||||
}
|
||||
|
||||
if len(roles) == 0 || applicationID == "" {
|
||||
return userInfo, nil
|
||||
return nil
|
||||
}
|
||||
projectRoles, err := o.assertRoles(ctx, userID, applicationID, roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if len(projectRoles) > 0 {
|
||||
userInfo.AppendClaims(ClaimProjectRoles, projectRoles)
|
||||
}
|
||||
|
||||
return userInfo, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection oidc.IntrospectionResponse, tokenID, subject, clientID string) error {
|
||||
token, err := o.repo.TokenByID(ctx, subject, tokenID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Dsfb2", "token is not valid or has expired")
|
||||
}
|
||||
app, err := o.repo.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-Adfg5", "client not found")
|
||||
}
|
||||
for _, aud := range token.Audience {
|
||||
if aud == clientID || aud == app.ProjectID {
|
||||
err := o.SetUserinfoFromScopes(ctx, introspection, token.UserID, clientID, token.Scopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
introspection.SetScopes(token.Scopes)
|
||||
introspection.SetClientID(token.ApplicationID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.ThrowPermissionDenied(nil, "OIDC-sdg3G", "token is not valid for this client")
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetPrivateClaimsFromScopes(ctx context.Context, userID, clientID string, scopes []string) (claims map[string]interface{}, err error) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
authreq_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
|
||||
authreq_model "github.com/caos/zitadel/internal/auth_request/model"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
)
|
||||
@@ -37,7 +37,7 @@ func (c *Client) ApplicationType() op.ApplicationType {
|
||||
return op.ApplicationType(c.OIDCApplicationType)
|
||||
}
|
||||
|
||||
func (c *Client) AuthMethod() op.AuthMethod {
|
||||
func (c *Client) AuthMethod() oidc.AuthMethod {
|
||||
return authMethodToOIDC(c.OIDCAuthMethodType)
|
||||
}
|
||||
|
||||
@@ -129,16 +129,18 @@ func accessTokenTypeToOIDC(tokenType model.OIDCTokenType) op.AccessTokenType {
|
||||
}
|
||||
}
|
||||
|
||||
func authMethodToOIDC(authType model.OIDCAuthMethodType) op.AuthMethod {
|
||||
func authMethodToOIDC(authType model.OIDCAuthMethodType) oidc.AuthMethod {
|
||||
switch authType {
|
||||
case model.OIDCAuthMethodTypeBasic:
|
||||
return op.AuthMethodBasic
|
||||
return oidc.AuthMethodBasic
|
||||
case model.OIDCAuthMethodTypePost:
|
||||
return op.AuthMethodPost
|
||||
return oidc.AuthMethodPost
|
||||
case model.OIDCAuthMethodTypeNone:
|
||||
return op.AuthMethodNone
|
||||
return oidc.AuthMethodNone
|
||||
case model.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
return oidc.AuthMethodPrivateKeyJWT
|
||||
default:
|
||||
return op.AuthMethodBasic
|
||||
return oidc.AuthMethodBasic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/telemetry/metrics"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/telemetry/metrics"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
|
||||
@@ -32,11 +33,12 @@ type StorageConfig struct {
|
||||
}
|
||||
|
||||
type EndpointConfig struct {
|
||||
Auth *Endpoint
|
||||
Token *Endpoint
|
||||
Userinfo *Endpoint
|
||||
EndSession *Endpoint
|
||||
Keys *Endpoint
|
||||
Auth *Endpoint
|
||||
Token *Endpoint
|
||||
Introspection *Endpoint
|
||||
Userinfo *Endpoint
|
||||
EndSession *Endpoint
|
||||
Keys *Endpoint
|
||||
}
|
||||
|
||||
type Endpoint struct {
|
||||
@@ -70,6 +72,7 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re
|
||||
),
|
||||
op.WithCustomAuthEndpoint(op.NewEndpointWithURL(config.Endpoints.Auth.Path, config.Endpoints.Auth.URL)),
|
||||
op.WithCustomTokenEndpoint(op.NewEndpointWithURL(config.Endpoints.Token.Path, config.Endpoints.Token.URL)),
|
||||
op.WithCustomIntrospectionEndpoint(op.NewEndpointWithURL(config.Endpoints.Introspection.Path, config.Endpoints.Introspection.URL)),
|
||||
op.WithCustomUserinfoEndpoint(op.NewEndpointWithURL(config.Endpoints.Userinfo.Path, config.Endpoints.Userinfo.URL)),
|
||||
op.WithCustomEndSessionEndpoint(op.NewEndpointWithURL(config.Endpoints.EndSession.Path, config.Endpoints.EndSession.URL)),
|
||||
op.WithCustomKeysEndpoint(op.NewEndpointWithURL(config.Endpoints.Keys.Path, config.Endpoints.Keys.URL)),
|
||||
|
||||
Reference in New Issue
Block a user