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:
Silvan
2020-09-17 08:49:33 +02:00
committed by GitHub
parent 71abc4f077
commit 5a8cafcae5
19 changed files with 335 additions and 88 deletions

View File

@@ -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
}

View File

@@ -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

View File

@@ -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),

View File

@@ -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
}

View File

@@ -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}},
}
}

View File

@@ -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)
}

View 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)
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -13,6 +13,7 @@ type MachineKeyView struct {
Sequence uint64
CreationDate time.Time
ExpirationDate time.Time
PublicKey []byte
}
type MachineKeySearchRequest struct {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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,
}
}