zitadel/internal/api/oidc/access_token.go
Tim Möhlmann 26d1563643
feat(api): feature flags (#7356)
* feat(api): feature API proto definitions

* update proto based on discussion with @livio-a

* cleanup old feature flag stuff

* authz instance queries

* align defaults

* projection definitions

* define commands and event reducers

* implement system and instance setter APIs

* api getter implementation

* unit test repository package

* command unit tests

* unit test Get queries

* grpc converter unit tests

* migrate the V1 features

* migrate oidc to dynamic features

* projection unit test

* fix instance by host

* fix instance by id data type in sql

* fix linting errors

* add system projection test

* fix behavior inversion

* resolve proto file comments

* rename SystemDefaultLoginInstanceEventType to SystemLoginDefaultOrgEventType so it's consistent with the instance level event

* use write models and conditional set events

* system features integration tests

* instance features integration tests

* error on empty request

* documentation entry

* typo in feature.proto

* fix start unit tests

* solve linting error on key case switch

* remove system defaults after discussion with @eliobischof

* fix system feature projection

* resolve comments in defaults.yaml

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
2024-02-28 10:55:54 +02:00

108 lines
3.4 KiB
Go

package oidc
import (
"context"
"errors"
"strings"
"time"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/user/model"
"github.com/zitadel/zitadel/internal/zerrors"
)
type accessToken struct {
tokenID string
userID string
subject string
clientID string
audience []string
scope []string
tokenCreation time.Time
tokenExpiration time.Time
isPAT bool
}
var ErrInvalidTokenFormat = errors.New("invalid token format")
func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (*accessToken, error) {
var tokenID, subject string
if tokenIDSubject, err := s.Provider().Crypto().Decrypt(tkn); err == nil {
split := strings.Split(tokenIDSubject, ":")
if len(split) != 2 {
return nil, zerrors.ThrowPermissionDenied(ErrInvalidTokenFormat, "OIDC-rei1O", "token is not valid or has expired")
}
tokenID, subject = split[0], split[1]
} else {
verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.accessTokenKeySet)
claims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, tkn, verifier)
if err != nil {
return nil, zerrors.ThrowPermissionDenied(err, "OIDC-Eib8e", "token is not valid or has expired")
}
tokenID, subject = claims.JWTID, claims.Subject
}
if strings.HasPrefix(tokenID, command.IDPrefixV2) {
token, err := s.query.ActiveAccessTokenByToken(ctx, tokenID)
if err != nil {
return nil, err
}
return accessTokenV2(tokenID, subject, token), nil
}
token, err := s.repo.TokenByIDs(ctx, subject, tokenID)
if err != nil {
return nil, zerrors.ThrowPermissionDenied(err, "OIDC-Dsfb2", "token is not valid or has expired")
}
return accessTokenV1(tokenID, subject, token), nil
}
func accessTokenV1(tokenID, subject string, token *model.TokenView) *accessToken {
return &accessToken{
tokenID: tokenID,
userID: token.UserID,
subject: subject,
clientID: token.ApplicationID,
audience: token.Audience,
scope: token.Scopes,
tokenCreation: token.CreationDate,
tokenExpiration: token.Expiration,
isPAT: token.IsPAT,
}
}
func accessTokenV2(tokenID, subject string, token *query.OIDCSessionAccessTokenReadModel) *accessToken {
return &accessToken{
tokenID: tokenID,
userID: token.UserID,
subject: subject,
clientID: token.ClientID,
audience: token.Audience,
scope: token.Scope,
tokenCreation: token.AccessTokenCreation,
tokenExpiration: token.AccessTokenExpiration,
}
}
func (s *Server) assertClientScopesForPAT(ctx context.Context, token *accessToken, clientID, projectID string) error {
token.audience = append(token.audience, clientID)
projectIDQuery, err := query.NewProjectRoleProjectIDSearchQuery(projectID)
if err != nil {
return zerrors.ThrowInternal(err, "OIDC-Cyc78", "Errors.Internal")
}
roles, err := s.query.SearchProjectRoles(ctx, authz.GetFeatures(ctx).TriggerIntrospectionProjections, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
if err != nil {
return err
}
for _, role := range roles.ProjectRoles {
token.scope = append(token.scope, ScopeProjectRolePrefix+role.Key)
}
return nil
}