mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 12:58:00 +00:00
118 lines
3.6 KiB
Go
118 lines
3.6 KiB
Go
|
package authz
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"crypto/rsa"
|
||
|
"errors"
|
||
|
"os"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/go-jose/go-jose/v3"
|
||
|
"github.com/zitadel/oidc/v3/pkg/op"
|
||
|
|
||
|
"github.com/zitadel/zitadel/internal/crypto"
|
||
|
zitadel_errors "github.com/zitadel/zitadel/internal/errors"
|
||
|
)
|
||
|
|
||
|
var _ SystemTokenVerifier = (*SystemTokenVerifierFromConfig)(nil)
|
||
|
|
||
|
type SystemTokenVerifierFromConfig struct {
|
||
|
systemJWTProfile *op.JWTProfileVerifier
|
||
|
systemUsers map[string]Memberships
|
||
|
}
|
||
|
|
||
|
func StartSystemTokenVerifierFromConfig(issuer string, keys map[string]*SystemAPIUser) (*SystemTokenVerifierFromConfig, error) {
|
||
|
systemUsers := make(map[string]Memberships, len(keys))
|
||
|
for userID, key := range keys {
|
||
|
if len(key.Memberships) == 0 {
|
||
|
systemUsers[userID] = Memberships{{MemberType: MemberTypeSystem, Roles: []string{"SYSTEM_OWNER"}}}
|
||
|
continue
|
||
|
}
|
||
|
for _, membership := range key.Memberships {
|
||
|
switch membership.MemberType {
|
||
|
case MemberTypeSystem, MemberTypeIAM, MemberTypeOrganization:
|
||
|
systemUsers[userID] = key.Memberships
|
||
|
case MemberTypeUnspecified, MemberTypeProject, MemberTypeProjectGrant:
|
||
|
return nil, errors.New("for system users, only the membership types System, IAM and Organization are supported")
|
||
|
default:
|
||
|
return nil, errors.New("unknown membership type")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return &SystemTokenVerifierFromConfig{
|
||
|
systemJWTProfile: op.NewJWTProfileVerifier(
|
||
|
&systemJWTStorage{
|
||
|
keys: keys,
|
||
|
cachedKeys: make(map[string]*rsa.PublicKey),
|
||
|
},
|
||
|
issuer,
|
||
|
1*time.Hour,
|
||
|
time.Second,
|
||
|
),
|
||
|
systemUsers: systemUsers,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (s *SystemTokenVerifierFromConfig) VerifySystemToken(ctx context.Context, token string, orgID string) (matchingMemberships Memberships, userID string, err error) {
|
||
|
jwtReq, err := op.VerifyJWTAssertion(ctx, token, s.systemJWTProfile)
|
||
|
if err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
systemUserMemberships, ok := s.systemUsers[jwtReq.Subject]
|
||
|
if !ok {
|
||
|
return nil, "", zitadel_errors.ThrowPermissionDenied(nil, "AUTH-Bohd2", "Errors.User.UserIDWrong")
|
||
|
}
|
||
|
matchingMemberships = make(Memberships, 0, len(systemUserMemberships))
|
||
|
for _, membership := range systemUserMemberships {
|
||
|
if membership.MemberType == MemberTypeSystem ||
|
||
|
membership.MemberType == MemberTypeIAM && GetInstance(ctx).InstanceID() == membership.AggregateID ||
|
||
|
membership.MemberType == MemberTypeOrganization && orgID == membership.AggregateID {
|
||
|
matchingMemberships = append(matchingMemberships, membership)
|
||
|
}
|
||
|
}
|
||
|
return matchingMemberships, jwtReq.Subject, nil
|
||
|
}
|
||
|
|
||
|
type systemJWTStorage struct {
|
||
|
keys map[string]*SystemAPIUser
|
||
|
mutex sync.Mutex
|
||
|
cachedKeys map[string]*rsa.PublicKey
|
||
|
}
|
||
|
|
||
|
type SystemAPIUser struct {
|
||
|
Path string // if a path is specified, the key will be read from that path
|
||
|
KeyData []byte // else you can also specify the data directly in the KeyData
|
||
|
Memberships Memberships
|
||
|
}
|
||
|
|
||
|
func (s *SystemAPIUser) readKey() (*rsa.PublicKey, error) {
|
||
|
if s.Path != "" {
|
||
|
var err error
|
||
|
s.KeyData, err = os.ReadFile(s.Path)
|
||
|
if err != nil {
|
||
|
return nil, zitadel_errors.ThrowInternal(err, "AUTHZ-JK31F", "Errors.NotFound")
|
||
|
}
|
||
|
}
|
||
|
return crypto.BytesToPublicKey(s.KeyData)
|
||
|
}
|
||
|
|
||
|
func (s *systemJWTStorage) GetKeyByIDAndClientID(_ context.Context, _, userID string) (*jose.JSONWebKey, error) {
|
||
|
cachedKey, ok := s.cachedKeys[userID]
|
||
|
if ok {
|
||
|
return &jose.JSONWebKey{KeyID: userID, Key: cachedKey}, nil
|
||
|
}
|
||
|
key, ok := s.keys[userID]
|
||
|
if !ok {
|
||
|
return nil, zitadel_errors.ThrowNotFound(nil, "AUTHZ-asfd3", "Errors.User.NotFound")
|
||
|
}
|
||
|
s.mutex.Lock()
|
||
|
defer s.mutex.Unlock()
|
||
|
publicKey, err := key.readKey()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
s.cachedKeys[userID] = publicKey
|
||
|
return &jose.JSONWebKey{KeyID: userID, Key: publicKey}, nil
|
||
|
}
|