mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:47:33 +00:00
fix: JWT Profile (#748)
* fix: correct env var for tracing type * fix: local env tracing * fix: key in detail as string * fix: implement storage * fix: machine key by id fix: store public key as bytes instead of crypto value * update oidc pkg * dont check origins for service account tokens * fix: scopes * fix: dependencies * fix: dependencies * fix: remove unused code * fix: variable naming Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
@@ -59,15 +59,14 @@ func (o *OPStorage) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||
return o.repo.DeleteAuthRequest(ctx, id)
|
||||
}
|
||||
|
||||
func (o *OPStorage) CreateToken(ctx context.Context, authReq op.AuthRequest) (string, time.Time, error) {
|
||||
app, err := o.repo.ApplicationByClientID(ctx, authReq.GetClientID())
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
func (o *OPStorage) CreateToken(ctx context.Context, req op.TokenRequest) (string, time.Time, error) {
|
||||
var userAgentID, applicationID string
|
||||
authReq, ok := req.(*AuthRequest)
|
||||
if ok {
|
||||
userAgentID = authReq.AgentID
|
||||
applicationID = authReq.ApplicationID
|
||||
}
|
||||
grants, err := o.repo.UserGrantsByProjectAndUserID(app.ProjectID, authReq.GetSubject())
|
||||
scopes := append(authReq.GetScopes(), grantsToScopes(grants)...)
|
||||
req, _ := authReq.(*AuthRequest)
|
||||
resp, err := o.repo.CreateToken(ctx, req.AgentID, req.ApplicationID, req.UserID, req.Audience, scopes, o.defaultAccessTokenLifetime) //PLANNED: lifetime from client
|
||||
resp, err := o.repo.CreateToken(ctx, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
@@ -2,14 +2,16 @@ package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
"github.com/caos/oidc/pkg/op"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
@@ -36,6 +38,25 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (op.Clie
|
||||
return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (*jose.JSONWebKey, error) {
|
||||
key, err := o.repo.MachineKeyByID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key.UserID != userID {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-24jm3", "key from different user")
|
||||
}
|
||||
publicKey, err := crypto.BytesToPublicKey(key.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &jose.JSONWebKey{
|
||||
KeyID: key.ID,
|
||||
Use: "sig",
|
||||
Key: publicKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) AuthorizeClientIDSecret(ctx context.Context, id string, secret string) error {
|
||||
ctx = authz.SetCtxData(ctx, authz.CtxData{
|
||||
UserID: oidcCtx,
|
||||
@@ -49,12 +70,14 @@ func (o *OPStorage) GetUserinfoFromToken(ctx context.Context, tokenID, origin st
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
if token.ApplicationID != "" {
|
||||
app, err := o.repo.ApplicationByClientID(ctx, token.ApplicationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if origin != "" && !http.IsOriginAllowed(app.OriginAllowList, origin) {
|
||||
return nil, errors.ThrowPermissionDenied(nil, "OIDC-da1f3", "origin is not allowed")
|
||||
}
|
||||
}
|
||||
return o.GetUserinfoFromScopes(ctx, token.UserID, token.Scopes)
|
||||
}
|
||||
@@ -70,22 +93,34 @@ func (o *OPStorage) GetUserinfoFromScopes(ctx context.Context, userID string, sc
|
||||
case scopeOpenID:
|
||||
userInfo.Subject = user.ID
|
||||
case scopeEmail:
|
||||
if user.HumanView == nil {
|
||||
continue
|
||||
}
|
||||
userInfo.Email = user.Email
|
||||
userInfo.EmailVerified = user.IsEmailVerified
|
||||
case scopeProfile:
|
||||
userInfo.Name = user.DisplayName
|
||||
userInfo.FamilyName = user.LastName
|
||||
userInfo.GivenName = user.FirstName
|
||||
userInfo.Nickname = user.NickName
|
||||
userInfo.PreferredUsername = user.PreferredLoginName
|
||||
userInfo.UpdatedAt = user.ChangeDate
|
||||
userInfo.Gender = oidc.Gender(getGender(user.Gender))
|
||||
userInfo.Locale, err = language.Parse(user.PreferredLanguage)
|
||||
logging.Log("OIDC-4ks9F").OnError(err).Debug("unable to parse locale")
|
||||
if user.HumanView != nil {
|
||||
userInfo.Name = user.DisplayName
|
||||
userInfo.FamilyName = user.LastName
|
||||
userInfo.GivenName = user.FirstName
|
||||
userInfo.Nickname = user.NickName
|
||||
userInfo.Gender = oidc.Gender(getGender(user.Gender))
|
||||
userInfo.Locale, err = language.Parse(user.PreferredLanguage)
|
||||
} else {
|
||||
userInfo.Name = user.MachineView.Name
|
||||
}
|
||||
case scopePhone:
|
||||
if user.HumanView == nil {
|
||||
continue
|
||||
}
|
||||
userInfo.PhoneNumber = user.Phone
|
||||
userInfo.PhoneNumberVerified = user.IsPhoneVerified
|
||||
case scopeAddress:
|
||||
if user.HumanView == nil {
|
||||
continue
|
||||
}
|
||||
userInfo.Address.StreetAddress = user.StreetAddress
|
||||
userInfo.Address.Locality = user.Locality
|
||||
userInfo.Address.Region = user.Region
|
||||
|
@@ -54,7 +54,7 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, repo repository.Re
|
||||
cookieHandler, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode)
|
||||
logging.Log("OIDC-sd4fd").OnError(err).Panic("cannot user agent handler")
|
||||
config.OPConfig.CodeMethodS256 = true
|
||||
provider, err := op.NewDefaultOP(
|
||||
provider, err := op.NewOpenIDProvider(
|
||||
ctx,
|
||||
config.OPConfig,
|
||||
newStorage(config.StorageConfig, repo),
|
||||
|
@@ -334,3 +334,11 @@ func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *UserRepo) MachineKeyByID(ctx context.Context, keyID string) (*model.MachineKeyView, error) {
|
||||
key, err := repo.View.MachineKeyByID(keyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usr_view_model.MachineKeyToModel(key), nil
|
||||
}
|
||||
|
@@ -52,6 +52,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, ev
|
||||
projectEvents: repos.ProjectEvents,
|
||||
iamEvents: repos.IamEvents,
|
||||
iamID: systemDefaults.IamID},
|
||||
&MachineKeys{handler: handler{view, bulkLimit, configs.cycleDuration("MachineKey"), errorCount}},
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,73 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
)
|
||||
|
||||
type MachineKeys struct {
|
||||
handler
|
||||
}
|
||||
|
||||
const (
|
||||
machineKeysTable = "auth.machine_keys"
|
||||
)
|
||||
|
||||
func (d *MachineKeys) ViewModel() string {
|
||||
return machineKeysTable
|
||||
}
|
||||
|
||||
func (d *MachineKeys) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := d.view.GetLatestMachineKeySequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(model.UserAggregate).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (d *MachineKeys) Reduce(event *models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case model.UserAggregate:
|
||||
err = d.processMachineKeys(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *MachineKeys) processMachineKeys(event *models.Event) (err error) {
|
||||
key := new(usr_model.MachineKeyView)
|
||||
switch event.Type {
|
||||
case model.MachineKeyAdded:
|
||||
err = key.AppendEvent(event)
|
||||
if key.ExpirationDate.Before(time.Now()) {
|
||||
return d.view.ProcessedMachineKeySequence(event.Sequence)
|
||||
}
|
||||
case model.MachineKeyRemoved:
|
||||
err = key.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.view.DeleteMachineKey(key.ID, event.Sequence)
|
||||
case model.UserRemoved:
|
||||
return d.view.DeleteMachineKeysByUserID(event.AggregateID, event.Sequence)
|
||||
default:
|
||||
return d.view.ProcessedMachineKeySequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.view.PutMachineKey(key, key.Sequence)
|
||||
}
|
||||
|
||||
func (d *MachineKeys) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-S9fe", "id", event.AggregateID).WithError(err).Warn("something went wrong in machine key handler")
|
||||
return spooler.HandleError(event, err, d.view.GetLatestMachineKeyFailedEvent, d.view.ProcessedMachineKeyFailedEvent, d.view.ProcessedMachineKeySequence, d.errorCountUntilSkip)
|
||||
}
|
71
internal/auth/repository/eventsourcing/view/machine_keys.go
Normal file
71
internal/auth/repository/eventsourcing/view/machine_keys.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/view"
|
||||
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
machineKeyTable = "auth.machine_keys"
|
||||
)
|
||||
|
||||
func (v *View) MachineKeyByIDs(userID, keyID string) (*model.MachineKeyView, error) {
|
||||
return view.MachineKeyByIDs(v.Db, machineKeyTable, userID, keyID)
|
||||
}
|
||||
|
||||
func (v *View) MachineKeysByUserID(userID string) ([]*model.MachineKeyView, error) {
|
||||
return view.MachineKeysByUserID(v.Db, machineKeyTable, userID)
|
||||
}
|
||||
|
||||
func (v *View) MachineKeyByID(keyID string) (*model.MachineKeyView, error) {
|
||||
return view.MachineKeyByID(v.Db, machineKeyTable, keyID)
|
||||
}
|
||||
|
||||
func (v *View) SearchMachineKeys(request *usr_model.MachineKeySearchRequest) ([]*model.MachineKeyView, uint64, error) {
|
||||
return view.SearchMachineKeys(v.Db, machineKeyTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutMachineKey(key *model.MachineKeyView, sequence uint64) error {
|
||||
err := view.PutMachineKey(v.Db, machineKeyTable, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sequence != 0 {
|
||||
return v.ProcessedMachineKeySequence(sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *View) DeleteMachineKey(keyID string, eventSequence uint64) error {
|
||||
err := view.DeleteMachineKey(v.Db, machineKeyTable, keyID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedMachineKeySequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) DeleteMachineKeysByUserID(userID string, eventSequence uint64) error {
|
||||
err := view.DeleteMachineKey(v.Db, machineKeyTable, userID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedMachineKeySequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestMachineKeySequence() (*repository.CurrentSequence, error) {
|
||||
return v.latestSequence(machineKeyTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMachineKeySequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(machineKeyTable, eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestMachineKeyFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
|
||||
return v.latestFailedEvent(machineKeyTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedMachineKeyFailedEvent(failedEvent *repository.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@@ -32,6 +32,8 @@ type UserRepository interface {
|
||||
SignOut(ctx context.Context, agentID string) error
|
||||
|
||||
UserByID(ctx context.Context, userID string) (*model.UserView, error)
|
||||
|
||||
MachineKeyByID(ctx context.Context, keyID string) (*model.MachineKeyView, error)
|
||||
}
|
||||
|
||||
type myUserRepo interface {
|
||||
|
@@ -26,6 +26,8 @@ Errors:
|
||||
AddressNotFound: Addresse nicht gefunden
|
||||
NotHuman: Der Benutzer muss eine Person sein
|
||||
NotMachine: Der Benutzer muss technisch sein
|
||||
Username:
|
||||
Reservied: Benutzername ist bereits vergeben
|
||||
Code:
|
||||
Empty: Code ist leer
|
||||
NotFound: Code konnte nicht gefunden werden
|
||||
|
@@ -26,6 +26,8 @@ Errors:
|
||||
AddressNotFound: Address not found
|
||||
NotHuman: The User must be personal
|
||||
NotMachine: The User must be technical
|
||||
Username:
|
||||
Reservied: Username is already taken
|
||||
Code:
|
||||
Empty: Code is empty
|
||||
NotFound: Code not found
|
||||
|
@@ -13,6 +13,7 @@ type MachineKeyView struct {
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ExpirationDate time.Time
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
type MachineKeySearchRequest struct {
|
||||
|
@@ -69,10 +69,10 @@ func MachineToModel(machine *Machine) *model.Machine {
|
||||
|
||||
type MachineKey struct {
|
||||
es_models.ObjectRoot `json:"-"`
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
Type int32 `json:"type,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey *crypto.CryptoValue `json:"publicKey,omitempty"`
|
||||
KeyID string `json:"keyId,omitempty"`
|
||||
Type int32 `json:"type,omitempty"`
|
||||
ExpirationDate time.Time `json:"expirationDate,omitempty"`
|
||||
PublicKey []byte `json:"publicKey,omitempty"`
|
||||
privateKey []byte
|
||||
}
|
||||
|
||||
@@ -117,11 +117,7 @@ func (key *MachineKey) GenerateMachineKeyPair(keySize int, alg crypto.Encryption
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKeyBytes, err := crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key.PublicKey, err = crypto.Encrypt(publicKeyBytes, alg)
|
||||
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -49,6 +49,18 @@ func MachineKeysByUserID(db *gorm.DB, table string, userID string) ([]*model.Mac
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func MachineKeyByID(db *gorm.DB, table string, keyID string) (*model.MachineKeyView, error) {
|
||||
key := new(model.MachineKeyView)
|
||||
query := repository.PrepareGetByQuery(table,
|
||||
model.MachineKeySearchQuery{Key: usr_model.MachineKeyKeyID, Method: global_model.SearchMethodEquals, Value: keyID},
|
||||
)
|
||||
err := query(db, key)
|
||||
if caos_errs.IsNotFound(err) {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "VIEW-BjN6x", "Errors.User.KeyNotFound")
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
func PutMachineKey(db *gorm.DB, table string, role *model.MachineKeyView) error {
|
||||
save := repository.PrepareSave(table)
|
||||
return save(db, role)
|
||||
|
@@ -25,6 +25,8 @@ type MachineKeyView struct {
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
|
||||
PublicKey []byte `json:"publicKey" gorm:"column:public_key"`
|
||||
}
|
||||
|
||||
func MachineKeyViewFromModel(key *model.MachineKeyView) *MachineKeyView {
|
||||
@@ -46,6 +48,7 @@ func MachineKeyToModel(key *MachineKeyView) *model.MachineKeyView {
|
||||
ExpirationDate: key.ExpirationDate,
|
||||
Sequence: key.Sequence,
|
||||
CreationDate: key.CreationDate,
|
||||
PublicKey: key.PublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user