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:
Livio Amstutz
2021-02-17 15:31:47 +01:00
committed by GitHub
parent 39eb172804
commit 744185449e
64 changed files with 2275 additions and 836 deletions

View File

@@ -0,0 +1,62 @@
package model
import (
"fmt"
"strings"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id"
)
type APIConfig struct {
es_models.ObjectRoot
AppID string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretString string
AuthMethodType APIAuthMethodType
ClientKeys []*ClientKey
}
type APIAuthMethodType int32
const (
APIAuthMethodTypeBasic APIAuthMethodType = iota
APIAuthMethodTypePrivateKeyJWT
)
func (c *APIConfig) IsValid() bool {
return true
}
//ClientID random_number@projectname (eg. 495894098234@zitadel)
func (c *APIConfig) GenerateNewClientID(idGenerator id.Generator, project *Project) error {
rndID, err := idGenerator.Next()
if err != nil {
return err
}
c.ClientID = fmt.Sprintf("%v@%v", rndID, strings.ReplaceAll(strings.ToLower(project.Name), " ", "_"))
return nil
}
func (c *APIConfig) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) {
if c.AuthMethodType == APIAuthMethodTypeBasic {
return c.GenerateNewClientSecret(generator)
}
return "", nil
}
func (c *APIConfig) GenerateNewClientSecret(generator crypto.Generator) (string, error) {
cryptoValue, stringSecret, err := crypto.NewCode(generator)
if err != nil {
logging.Log("MODEL-ADvd2").OnError(err).Error("unable to create client secret")
return "", errors.ThrowInternal(err, "MODEL-dsvr43", "Errors.Project.CouldNotGenerateClientSecret")
}
c.ClientSecret = cryptoValue
return stringSecret, nil
}

View File

@@ -1,8 +1,9 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/golang/protobuf/ptypes/timestamp"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type Application struct {
@@ -13,6 +14,7 @@ type Application struct {
Name string
Type AppType
OIDCConfig *OIDCConfig
APIConfig *APIConfig
}
type ApplicationChanges struct {
Changes []*ApplicationChange
@@ -42,6 +44,7 @@ const (
AppTypeUnspecified AppType = iota
AppTypeOIDC
AppTypeSAML
AppTypeAPI
)
func NewApplication(projectID, appID string) *Application {
@@ -58,5 +61,20 @@ func (a *Application) IsValid(includeConfig bool) bool {
if a.Type == AppTypeOIDC && !a.OIDCConfig.IsValid() {
return false
}
if a.Type == AppTypeAPI && !a.APIConfig.IsValid() {
return false
}
return true
}
func (a *Application) GetKey(keyID string) (int, *ClientKey) {
if a.OIDCConfig == nil {
return -1, nil
}
for i, k := range a.OIDCConfig.ClientKeys {
if k.KeyID == keyID {
return i, k
}
}
return -1, nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id"
key_model "github.com/caos/zitadel/internal/key/model"
)
const (
@@ -40,6 +41,7 @@ type OIDCConfig struct {
IDTokenRoleAssertion bool
IDTokenUserinfoAssertion bool
ClockSkew time.Duration
ClientKeys []*ClientKey
}
type OIDCVersion int32
@@ -78,6 +80,7 @@ const (
OIDCAuthMethodTypeBasic OIDCAuthMethodType = iota
OIDCAuthMethodTypePost
OIDCAuthMethodTypeNone
OIDCAuthMethodTypePrivateKeyJWT
)
type Compliance struct {
@@ -92,6 +95,27 @@ const (
OIDCTokenTypeJWT
)
type ClientKey struct {
es_models.ObjectRoot
ApplicationID string
ClientID string
KeyID string
Type key_model.AuthNKeyType
ExpirationDate time.Time
PrivateKey []byte
}
type Token struct {
es_models.ObjectRoot
TokenID string
ClientID string
Audience []string
Expiration time.Time
Scopes []string
}
func (c *OIDCConfig) IsValid() bool {
grantTypes := c.getRequiredGrantTypes()
for _, grantType := range grantTypes {
@@ -115,10 +139,10 @@ func (c *OIDCConfig) GenerateNewClientID(idGenerator id.Generator, project *Proj
}
func (c *OIDCConfig) GenerateClientSecretIfNeeded(generator crypto.Generator) (string, error) {
if c.AuthMethodType == OIDCAuthMethodTypeNone {
return "", nil
if c.AuthMethodType == OIDCAuthMethodTypeBasic || c.AuthMethodType == OIDCAuthMethodTypePost {
return c.GenerateNewClientSecret(generator)
}
return c.GenerateNewClientSecret(generator)
return "", nil
}
func (c *OIDCConfig) GenerateNewClientSecret(generator crypto.Generator) (string, error) {

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"strings"
"time"
"github.com/caos/logging"
"github.com/golang/protobuf/ptypes"
@@ -18,6 +19,7 @@ import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
"github.com/caos/zitadel/internal/id"
key_model "github.com/caos/zitadel/internal/key/model"
proj_model "github.com/caos/zitadel/internal/project/model"
"github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/telemetry/tracing"
@@ -30,10 +32,11 @@ const (
type ProjectEventstore struct {
es_int.Eventstore
projectCache *ProjectCache
passwordAlg crypto.HashAlgorithm
pwGenerator crypto.Generator
idGenerator id.Generator
projectCache *ProjectCache
passwordAlg crypto.HashAlgorithm
pwGenerator crypto.Generator
idGenerator id.Generator
ClientKeySize int
}
type ProjectConfig struct {
@@ -49,11 +52,12 @@ func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*Projec
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
return &ProjectEventstore{
Eventstore: conf.Eventstore,
projectCache: projectCache,
passwordAlg: passwordAlg,
pwGenerator: pwGenerator,
idGenerator: id.SonyFlakeGenerator,
Eventstore: conf.Eventstore,
projectCache: projectCache,
passwordAlg: passwordAlg,
pwGenerator: pwGenerator,
idGenerator: id.SonyFlakeGenerator,
ClientKeySize: int(systemDefaults.SecretGenerators.ClientKeySize),
}, nil
}
@@ -513,15 +517,14 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model
if err != nil {
return nil, err
}
id, err := es.idGenerator.Next()
app.AppID, err = es.idGenerator.Next()
if err != nil {
return nil, err
}
app.AppID = id
var stringPw string
if app.OIDCConfig != nil {
app.OIDCConfig.AppID = id
app.OIDCConfig.AppID = app.AppID
err := app.OIDCConfig.GenerateNewClientID(es.idGenerator, existingProject)
if err != nil {
return nil, err
@@ -531,6 +534,17 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model
return nil, err
}
}
if app.APIConfig != nil {
app.APIConfig.AppID = app.AppID
err := app.APIConfig.GenerateNewClientID(es.idGenerator, existingProject)
if err != nil {
return nil, err
}
stringPw, err = app.APIConfig.GenerateClientSecretIfNeeded(es.pwGenerator)
if err != nil {
return nil, err
}
}
repoProject := model.ProjectFromModel(existingProject)
repoApp := model.AppFromModel(app)
@@ -542,8 +556,13 @@ func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
converted := model.AppToModel(a)
converted.OIDCConfig.ClientSecretString = stringPw
converted.OIDCConfig.FillCompliance()
if converted.OIDCConfig != nil {
converted.OIDCConfig.ClientSecretString = stringPw
converted.OIDCConfig.FillCompliance()
}
if converted.APIConfig != nil {
converted.APIConfig.ClientSecretString = stringPw
}
return converted, nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-GvPct", "Errors.Internal")
@@ -748,6 +767,36 @@ func (es *ProjectEventstore) ChangeOIDCConfig(ctx context.Context, config *proj_
return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal")
}
func (es *ProjectEventstore) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) {
if config == nil || !config.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-SDg54", "Errors.Project.APIConfigInvalid")
}
existingProject, err := es.ProjectByID(ctx, config.AggregateID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(config.AppID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Rgu63", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeAPI {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-RHj63", "Errors.Project.AppIsNotAPI")
}
repoProject := model.ProjectFromModel(existingProject)
repoConfig := model.APIConfigFromModel(config)
projectAggregate := APIConfigChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoConfig)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
return model.APIConfigToModel(a.APIConfig), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-aebn5", "Errors.Internal")
}
func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.OIDCConfig, error) {
if appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-7ue34", "Errors.Project.OIDCConfigInvalid")
@@ -763,8 +812,8 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project
if app.Type != proj_model.AppTypeOIDC {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dile4", "Errors.Project.AppIsNotOIDC")
}
if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoneSecret")
if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone || app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypePrivateKeyJWT {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoSecret")
}
repoProject := model.ProjectFromModel(existingProject)
@@ -789,6 +838,47 @@ func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, project
return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal")
}
func (es *ProjectEventstore) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) {
if appID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdfb3", "Errors.Project.APIConfigInvalid")
}
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(appID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADbg3", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeAPI {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Ntwqw", "Errors.Project.AppIsNotAPI")
}
if app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypeBasic {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-HW4tw", "Errors.Project.APIAuthMethodNoSecret")
}
repoProject := model.ProjectFromModel(existingProject)
stringPw, err := app.APIConfig.GenerateNewClientSecret(es.pwGenerator)
if err != nil {
return nil, err
}
projectAggregate := APIConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.APIConfig.ClientSecret)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
config := model.APIConfigToModel(a.APIConfig)
config.ClientSecretString = stringPw
return config, nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-HBfju", "Errors.Internal")
}
func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -831,6 +921,81 @@ func (es *ProjectEventstore) setOIDCClientSecretCheckResult(ctx context.Context,
return nil
}
func (es *ProjectEventstore) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) {
existingProject, err := es.ProjectByID(ctx, key.AggregateID)
if err != nil {
return nil, err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(key.ApplicationID); app == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbf32", "Errors.Project.AppNoExisting")
}
if (app.OIDCConfig == nil || app.OIDCConfig != nil && app.OIDCConfig.AuthMethodType != proj_model.OIDCAuthMethodTypePrivateKeyJWT) &&
(app.APIConfig == nil || app.APIConfig != nil && app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypePrivateKeyJWT) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dff54", "Errors.Project.AuthMethodNoPrivateKeyJWT")
}
if app.OIDCConfig != nil {
key.ClientID = app.OIDCConfig.ClientID
}
if app.APIConfig != nil {
key.ClientID = app.APIConfig.ClientID
}
key.KeyID, err = es.idGenerator.Next()
if err != nil {
return nil, err
}
if key.ExpirationDate.IsZero() {
key.ExpirationDate, err = key_model.DefaultExpiration()
if err != nil {
logging.Log("EVENT-Adgf2").WithError(err).Warn("unable to set default date")
return nil, errors.ThrowInternal(err, "EVENT-j68fg", "Errors.Internal")
}
}
if key.ExpirationDate.Before(time.Now()) {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-C6YV5", "Errors.MachineKey.ExpireBeforeNow")
}
repoProject := model.ProjectFromModel(existingProject)
repoKey := model.ClientKeyFromModel(key)
err = repoKey.GenerateClientKeyPair(es.ClientKeySize)
if err != nil {
return nil, err
}
agg := OIDCApplicationKeyAddedAggregate(es.AggregateCreator(), repoProject, repoKey)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
if err != nil {
return nil, err
}
es.projectCache.cacheProject(repoProject)
return model.ClientKeyToModel(repoKey), nil
}
func (es *ProjectEventstore) RemoveApplicationKey(ctx context.Context, projectID, applicationID, keyID string) error {
existingProject, err := es.ProjectByID(ctx, projectID)
if err != nil {
return err
}
var app *proj_model.Application
if _, app = existingProject.GetApp(applicationID); app == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADfzz", "Errors.Project.AppNotExisting")
}
if app.Type != proj_model.AppTypeOIDC {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADffh", "Errors.Project.AppIsNotOIDC")
}
if _, key := app.GetKey(keyID); key == nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D2Sff", "Errors.Project.AppKeyNotExisting")
}
repoProject := model.ProjectFromModel(existingProject)
agg := OIDCApplicationKeyRemovedAggregate(es.AggregateCreator(), repoProject, keyID)
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
if err != nil {
return err
}
es.projectCache.cacheProject(repoProject)
return nil
}
func (es *ProjectEventstore) ProjectGrantByIDs(ctx context.Context, projectID, grantID string) (*proj_model.ProjectGrant, error) {
if grantID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-e8die", "Errors.Project.IDMissing")

View File

@@ -0,0 +1,87 @@
package model
import (
"encoding/json"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
)
type APIConfig struct {
es_models.ObjectRoot
AppID string `json:"appId"`
ClientID string `json:"clientId,omitempty"`
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
AuthMethodType int32 `json:"authMethodType,omitempty"`
ClientKeys []*ClientKey `json:"-"`
}
func (c *APIConfig) Changes(changed *APIConfig) map[string]interface{} {
changes := make(map[string]interface{}, 1)
changes["appId"] = c.AppID
if c.AuthMethodType != changed.AuthMethodType {
changes["authMethodType"] = changed.AuthMethodType
}
return changes
}
func APIConfigFromModel(config *model.APIConfig) *APIConfig {
return &APIConfig{
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
AuthMethodType: int32(config.AuthMethodType),
}
}
func APIConfigToModel(config *APIConfig) *model.APIConfig {
oidcConfig := &model.APIConfig{
ObjectRoot: config.ObjectRoot,
AppID: config.AppID,
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
AuthMethodType: model.APIAuthMethodType(config.AuthMethodType),
ClientKeys: ClientKeysToModel(config.ClientKeys),
}
return oidcConfig
}
func (p *Project) appendAddAPIConfigEvent(event *es_models.Event) error {
config := new(APIConfig)
err := config.setData(event)
if err != nil {
return err
}
config.ObjectRoot.CreationDate = event.CreationDate
if i, a := GetApplication(p.Applications, config.AppID); a != nil {
p.Applications[i].Type = int32(model.AppTypeAPI)
p.Applications[i].APIConfig = config
}
return nil
}
func (p *Project) appendChangeAPIConfigEvent(event *es_models.Event) error {
config := new(APIConfig)
err := config.setData(event)
if err != nil {
return err
}
if i, a := GetApplication(p.Applications, config.AppID); a != nil {
return p.Applications[i].APIConfig.setData(event)
}
return nil
}
func (o *APIConfig) setData(event *es_models.Event) error {
o.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, o); err != nil {
logging.Log("EVEN-d8e3s").WithError(err).Error("could not unmarshal event data")
return err
}
return nil
}

View File

@@ -2,7 +2,9 @@ package model
import (
"encoding/json"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model"
)
@@ -14,6 +16,7 @@ type Application struct {
Name string `json:"name,omitempty"`
Type int32 `json:"appType,omitempty"`
OIDCConfig *OIDCConfig `json:"-"`
APIConfig *APIConfig `json:"-"`
}
type ApplicationID struct {
@@ -66,6 +69,9 @@ func AppFromModel(app *model.Application) *Application {
if app.OIDCConfig != nil {
converted.OIDCConfig = OIDCConfigFromModel(app.OIDCConfig)
}
if app.APIConfig != nil {
converted.APIConfig = APIConfigFromModel(app.APIConfig)
}
return converted
}
@@ -80,6 +86,9 @@ func AppToModel(app *Application) *model.Application {
if app.OIDCConfig != nil {
converted.OIDCConfig = OIDCConfigToModel(app.OIDCConfig)
}
if app.APIConfig != nil {
converted.APIConfig = APIConfigToModel(app.APIConfig)
}
return converted
}
@@ -101,7 +110,7 @@ func (p *Project) appendChangeAppEvent(event *es_models.Event) error {
return err
}
if i, a := GetApplication(p.Applications, app.AppID); a != nil {
p.Applications[i].setData(event)
return p.Applications[i].setData(event)
}
return nil
}

View File

@@ -8,7 +8,9 @@ import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
key_model "github.com/caos/zitadel/internal/key/model"
"github.com/caos/zitadel/internal/project/model"
)
@@ -30,6 +32,7 @@ type OIDCConfig struct {
IDTokenRoleAssertion bool `json:"idTokenRoleAssertion,omitempty"`
IDTokenUserinfoAssertion bool `json:"idTokenUserinfoAssertion,omitempty"`
ClockSkew time.Duration `json:"clockSkew,omitempty"`
ClientKeys []*ClientKey `json:"-"`
}
func (c *OIDCConfig) Changes(changed *OIDCConfig) map[string]interface{} {
@@ -134,6 +137,7 @@ func OIDCConfigToModel(config *OIDCConfig) *model.OIDCConfig {
IDTokenRoleAssertion: config.IDTokenRoleAssertion,
IDTokenUserinfoAssertion: config.IDTokenUserinfoAssertion,
ClockSkew: config.ClockSkew,
ClientKeys: ClientKeysToModel(config.ClientKeys),
}
oidcConfig.FillCompliance()
return oidcConfig
@@ -161,7 +165,50 @@ func (p *Project) appendChangeOIDCConfigEvent(event *es_models.Event) error {
}
if i, a := GetApplication(p.Applications, config.AppID); a != nil {
p.Applications[i].OIDCConfig.setData(event)
return p.Applications[i].OIDCConfig.setData(event)
}
return nil
}
func (p *Project) appendAddClientKeyEvent(event *es_models.Event) error {
key := new(ClientKey)
err := key.SetData(event)
if err != nil {
return err
}
if i, a := GetApplication(p.Applications, key.ApplicationID); a != nil {
if a.OIDCConfig != nil {
p.Applications[i].OIDCConfig.ClientKeys = append(p.Applications[i].OIDCConfig.ClientKeys, key)
}
if a.APIConfig != nil {
p.Applications[i].APIConfig.ClientKeys = append(p.Applications[i].APIConfig.ClientKeys, key)
}
}
return nil
}
func (p *Project) appendRemoveClientKeyEvent(event *es_models.Event) error {
key := new(ClientKey)
err := key.SetData(event)
if err != nil {
return err
}
if i, a := GetApplication(p.Applications, key.ApplicationID); a != nil {
if a.OIDCConfig != nil {
if j, k := GetClientKey(p.Applications[i].OIDCConfig.ClientKeys, key.KeyID); k != nil {
p.Applications[i].OIDCConfig.ClientKeys[j] = p.Applications[i].OIDCConfig.ClientKeys[len(p.Applications[i].OIDCConfig.ClientKeys)-1]
p.Applications[i].OIDCConfig.ClientKeys[len(p.Applications[i].OIDCConfig.ClientKeys)-1] = nil
p.Applications[i].OIDCConfig.ClientKeys = p.Applications[i].OIDCConfig.ClientKeys[:len(p.Applications[i].OIDCConfig.ClientKeys)-1]
}
}
if a.APIConfig != nil {
if j, k := GetClientKey(p.Applications[i].APIConfig.ClientKeys, key.KeyID); k != nil {
p.Applications[i].APIConfig.ClientKeys[j] = p.Applications[i].APIConfig.ClientKeys[len(p.Applications[i].APIConfig.ClientKeys)-1]
p.Applications[i].APIConfig.ClientKeys[len(p.Applications[i].APIConfig.ClientKeys)-1] = nil
p.Applications[i].APIConfig.ClientKeys = p.Applications[i].APIConfig.ClientKeys[:len(p.Applications[i].APIConfig.ClientKeys)-1]
}
}
}
return nil
}
@@ -174,3 +221,100 @@ func (o *OIDCConfig) setData(event *es_models.Event) error {
}
return nil
}
func GetClientKey(keys []*ClientKey, id string) (int, *ClientKey) {
for i, k := range keys {
if k.KeyID == id {
return i, k
}
}
return -1, nil
}
type ClientKey struct {
es_models.ObjectRoot `json:"-"`
ApplicationID string `json:"applicationId,omitempty"`
ClientID string `json:"clientId,omitempty"`
KeyID string `json:"keyId,omitempty"`
Type int32 `json:"type,omitempty"`
ExpirationDate time.Time `json:"expirationDate,omitempty"`
PublicKey []byte `json:"publicKey,omitempty"`
privateKey []byte
}
func (key *ClientKey) SetData(event *es_models.Event) error {
key.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, key); err != nil {
logging.Log("EVEN-SADdg").WithError(err).Error("could not unmarshal event data")
return err
}
return nil
}
func (key *ClientKey) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
err := key.AppendEvent(event)
if err != nil {
return err
}
}
return nil
}
func (key *ClientKey) AppendEvent(event *es_models.Event) (err error) {
key.ObjectRoot.AppendEvent(event)
switch event.Type {
case ClientKeyAdded:
err = json.Unmarshal(event.Data, key)
if err != nil {
return errors.ThrowInternal(err, "MODEL-Fetg3", "Errors.Internal")
}
case ClientKeyRemoved:
key.ExpirationDate = event.CreationDate
}
return err
}
func ClientKeyFromModel(key *model.ClientKey) *ClientKey {
return &ClientKey{
ObjectRoot: key.ObjectRoot,
ExpirationDate: key.ExpirationDate,
ApplicationID: key.ApplicationID,
ClientID: key.ClientID,
KeyID: key.KeyID,
Type: int32(key.Type),
}
}
func ClientKeysToModel(keys []*ClientKey) []*model.ClientKey {
clientKeys := make([]*model.ClientKey, len(keys))
for i, key := range keys {
clientKeys[i] = ClientKeyToModel(key)
}
return clientKeys
}
func ClientKeyToModel(key *ClientKey) *model.ClientKey {
return &model.ClientKey{
ObjectRoot: key.ObjectRoot,
ExpirationDate: key.ExpirationDate,
ApplicationID: key.ApplicationID,
ClientID: key.ClientID,
KeyID: key.KeyID,
PrivateKey: key.privateKey,
Type: key_model.AuthNKeyType(key.Type),
}
}
func (key *ClientKey) GenerateClientKeyPair(keySize int) error {
privateKey, publicKey, err := crypto.GenerateKeyPair(keySize)
if err != nil {
return err
}
key.PublicKey, err = crypto.PublicKeyToBytes(publicKey)
if err != nil {
return err
}
key.privateKey = crypto.PrivateKeyToBytes(privateKey)
return nil
}

View File

@@ -139,6 +139,14 @@ func (p *Project) AppendEvent(event *es_models.Event) error {
return p.appendAddOIDCConfigEvent(event)
case OIDCConfigChanged, OIDCConfigSecretChanged:
return p.appendChangeOIDCConfigEvent(event)
case APIConfigAdded:
return p.appendAddAPIConfigEvent(event)
case APIConfigChanged, APIConfigSecretChanged:
return p.appendChangeAPIConfigEvent(event)
case ClientKeyAdded:
return p.appendAddClientKeyEvent(event)
case ClientKeyRemoved:
return p.appendRemoveClientKeyEvent(event)
case ProjectGrantAdded:
return p.appendAddGrantEvent(event)
case ProjectGrantChanged, ProjectGrantCascadeChanged:

View File

@@ -41,4 +41,11 @@ const (
OIDCConfigSecretChanged models.EventType = "project.application.config.oidc.secret.changed"
OIDCClientSecretCheckSucceeded models.EventType = "project.application.oidc.secret.check.succeeded"
OIDCClientSecretCheckFailed models.EventType = "project.application.oidc.secret.check.failed"
APIConfigAdded models.EventType = "project.application.config.api.added"
APIConfigChanged models.EventType = "project.application.config.api.changed"
APIConfigSecretChanged models.EventType = "project.application.config.api.secret.changed"
ClientKeyAdded models.EventType = "project.application.oidc.key.added"
ClientKeyRemoved models.EventType = "project.application.oidc.key.removed"
)

View File

@@ -36,6 +36,14 @@ func ProjectAggregate(ctx context.Context, aggCreator *es_models.AggregateCreato
return aggCreator.NewAggregate(ctx, project.AggregateID, model.ProjectAggregate, model.ProjectVersion, project.Sequence)
}
func ProjectAggregateOverwriteContext(ctx context.Context, aggCreator *es_models.AggregateCreator, project *model.Project, resourceOwnerID string, userID string) (*es_models.Aggregate, error) {
if project == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ADv2r", "Errors.Internal")
}
return aggCreator.NewAggregate(ctx, project.AggregateID, model.ProjectAggregate, model.ProjectVersion, project.Sequence, es_models.OverwriteResourceOwner(resourceOwnerID), es_models.OverwriteEditorUser(userID))
}
func ProjectCreateAggregate(aggCreator *es_models.AggregateCreator, project *model.Project, member *model.ProjectMember) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if project == nil || member == nil {
@@ -229,6 +237,9 @@ func ApplicationAddedAggregate(aggCreator *es_models.AggregateCreator, existingP
if app.OIDCConfig != nil {
agg.AppendEvent(model.OIDCConfigAdded, app.OIDCConfig)
}
if app.APIConfig != nil {
agg.AppendEvent(model.APIConfigAdded, app.APIConfig)
}
return agg, nil
}
}
@@ -322,6 +333,29 @@ func OIDCConfigChangedAggregate(aggCreator *es_models.AggregateCreator, existing
}
}
func APIConfigChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, config *model.APIConfig) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if config == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-slf32", "Errors.Internal")
}
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
if err != nil {
return nil, err
}
var changes map[string]interface{}
for _, a := range existingProject.Applications {
if a.AppID == config.AppID {
if a.APIConfig != nil {
changes = a.APIConfig.Changes(config)
}
}
}
agg.AppendEvent(model.APIConfigChanged, changes)
return agg, nil
}
}
func OIDCConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string, secret *crypto.CryptoValue) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
@@ -338,6 +372,22 @@ func OIDCConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, ex
}
}
func APIConfigSecretChangedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string, secret *crypto.CryptoValue) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
if err != nil {
return nil, err
}
changes := make(map[string]interface{}, 2)
changes["appId"] = appID
changes["clientSecret"] = secret
agg.AppendEvent(model.APIConfigSecretChanged, changes)
return agg, nil
}
}
func OIDCClientSecretCheckSucceededAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, appID string) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
@@ -368,6 +418,33 @@ func OIDCClientSecretCheckFailedAggregate(aggCreator *es_models.AggregateCreator
}
}
func OIDCApplicationKeyAddedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, key *model.ClientKey) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
if err != nil {
return nil, err
}
agg.AppendEvent(model.ClientKeyAdded, key)
return agg, nil
}
}
func OIDCApplicationKeyRemovedAggregate(aggCreator *es_models.AggregateCreator, existingProject *model.Project, keyID string) es_sdk.AggregateFunc {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := ProjectAggregate(ctx, aggCreator, existingProject)
if err != nil {
return nil, err
}
changes := make(map[string]interface{}, 1)
changes["keyId"] = keyID
agg.AppendEvent(model.ClientKeyRemoved, changes)
return agg, nil
}
}
func ProjectGrantAddedAggregate(aggCreator *es_models.AggregateCreator, project *model.Project, grant *model.ProjectGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if grant == nil {

View File

@@ -116,17 +116,25 @@ func (a *ApplicationView) AppendEventIfMyApp(event *models.Event) (err error) {
switch event.Type {
case es_model.ApplicationAdded:
err = view.SetData(event)
if err != nil {
return err
}
case es_model.ApplicationChanged,
es_model.OIDCConfigAdded,
es_model.OIDCConfigChanged,
es_model.APIConfigAdded,
es_model.APIConfigChanged,
es_model.ApplicationDeactivated,
es_model.ApplicationReactivated:
err := view.SetData(event)
err = view.SetData(event)
if err != nil {
return err
}
case es_model.ApplicationRemoved:
return view.SetData(event)
err = view.SetData(event)
if err != nil {
return err
}
case es_model.ProjectChanged:
return a.AppendEvent(event)
case es_model.ProjectRemoved:
@@ -156,14 +164,20 @@ func (a *ApplicationView) AppendEvent(event *models.Event) (err error) {
}
a.setCompliance()
return a.setOriginAllowList()
case es_model.OIDCConfigChanged,
es_model.ApplicationChanged:
case es_model.APIConfigAdded:
a.IsOIDC = false
return a.SetData(event)
case es_model.ApplicationChanged:
return a.SetData(event)
case es_model.OIDCConfigChanged:
err = a.SetData(event)
if err != nil {
return err
}
a.setCompliance()
return a.setOriginAllowList()
case es_model.APIConfigChanged:
return a.SetData(event)
case es_model.ProjectChanged:
return a.SetData(event)
case es_model.ApplicationDeactivated: