feat: OIDC setting (#3245)

* feat: add oidc config struct

* feat: oidc config command side

* feat: oidc configuration query side

* feat: add translations

* feat: add tests

* feat: add translations

* feat: rename oidc config to oidc settings

* feat: rename oidc config to oidc settings
This commit is contained in:
Fabi
2022-02-25 16:05:06 +01:00
committed by GitHub
parent f05d4063bf
commit 7d6c933485
57 changed files with 1440 additions and 40 deletions

View File

@@ -0,0 +1,79 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/iam"
)
func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
if err != nil {
return nil, err
}
if oidcSettingWriteModel.State.Exists() {
return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-d9nlw", "Errors.OIDCSettings.AlreadyExists")
}
iamAgg := IAMAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, iam.NewOIDCSettingsAddedEvent(
ctx,
iamAgg,
settings.AccessTokenLifetime,
settings.IdTokenLifetime,
settings.RefreshTokenIdleExpiration,
settings.RefreshTokenExpiration))
if err != nil {
return nil, err
}
err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil
}
func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) {
oidcSettingWriteModel, err := c.getOIDCSettings(ctx)
if err != nil {
return nil, err
}
if !oidcSettingWriteModel.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-8snEr", "Errors.OIDCSettings.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel)
changedEvent, hasChanged, err := oidcSettingWriteModel.NewChangedEvent(
ctx,
iamAgg,
settings.AccessTokenLifetime,
settings.IdTokenLifetime,
settings.RefreshTokenIdleExpiration,
settings.RefreshTokenExpiration)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-398uF", "Errors.NoChangesFound")
}
pushedEvents, err := c.eventstore.Push(ctx, changedEvent)
if err != nil {
return nil, err
}
err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil
}
func (c *Commands) getOIDCSettings(ctx context.Context) (_ *IAMOIDCSettingsWriteModel, err error) {
writeModel := NewIAMOIDCSettingsWriteModel()
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@@ -0,0 +1,101 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
)
type IAMOIDCSettingsWriteModel struct {
eventstore.WriteModel
AccessTokenLifetime time.Duration
IdTokenLifetime time.Duration
RefreshTokenIdleExpiration time.Duration
RefreshTokenExpiration time.Duration
State domain.OIDCSettingsState
}
func NewIAMOIDCSettingsWriteModel() *IAMOIDCSettingsWriteModel {
return &IAMOIDCSettingsWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
}
}
func (wm *IAMOIDCSettingsWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *iam.OIDCSettingsAddedEvent:
wm.AccessTokenLifetime = e.AccessTokenLifetime
wm.IdTokenLifetime = e.IdTokenLifetime
wm.RefreshTokenIdleExpiration = e.RefreshTokenIdleExpiration
wm.RefreshTokenExpiration = e.RefreshTokenExpiration
wm.State = domain.OIDCSettingsStateActive
case *iam.OIDCSettingsChangedEvent:
if e.AccessTokenLifetime != nil {
wm.AccessTokenLifetime = *e.AccessTokenLifetime
}
if e.IdTokenLifetime != nil {
wm.IdTokenLifetime = *e.IdTokenLifetime
}
if e.RefreshTokenIdleExpiration != nil {
wm.RefreshTokenIdleExpiration = *e.RefreshTokenIdleExpiration
}
if e.RefreshTokenExpiration != nil {
wm.RefreshTokenExpiration = *e.RefreshTokenExpiration
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *IAMOIDCSettingsWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(iam.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
iam.OIDCSettingsAddedEventType,
iam.OIDCSettingsChangedEventType).
Builder()
}
func (wm *IAMOIDCSettingsWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
accessTokenLifetime,
idTokenLifetime,
refreshTokenIdleExpiration,
refreshTokenExpiration time.Duration,
) (*iam.OIDCSettingsChangedEvent, bool, error) {
changes := make([]iam.OIDCSettingsChanges, 0, 4)
var err error
if wm.AccessTokenLifetime != accessTokenLifetime {
changes = append(changes, iam.ChangeOIDCSettingsAccessTokenLifetime(accessTokenLifetime))
}
if wm.IdTokenLifetime != idTokenLifetime {
changes = append(changes, iam.ChangeOIDCSettingsIdTokenLifetime(idTokenLifetime))
}
if wm.RefreshTokenIdleExpiration != refreshTokenIdleExpiration {
changes = append(changes, iam.ChangeOIDCSettingsRefreshTokenIdleExpiration(refreshTokenIdleExpiration))
}
if wm.RefreshTokenExpiration != refreshTokenExpiration {
changes = append(changes, iam.ChangeOIDCSettingsRefreshTokenExpiration(refreshTokenExpiration))
}
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := iam.NewOIDCSettingsChangeEvent(ctx, aggregate, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@@ -0,0 +1,264 @@
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/iam"
)
func TestCommandSide_AddOIDCConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
oidcConfig *domain.OIDCSettings
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "oidc config, error already exists",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOIDCSettingsAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
),
),
),
),
},
args: args{
ctx: context.Background(),
oidcConfig: &domain.OIDCSettings{
AccessTokenLifetime: 1 * time.Hour,
IdTokenLifetime: 1 * time.Hour,
RefreshTokenIdleExpiration: 1 * time.Hour,
RefreshTokenExpiration: 1 * time.Hour,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add secret generator, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(iam.NewOIDCSettingsAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
oidcConfig: &domain.OIDCSettings{
AccessTokenLifetime: 1 * time.Hour,
IdTokenLifetime: 1 * time.Hour,
RefreshTokenIdleExpiration: 1 * time.Hour,
RefreshTokenExpiration: 1 * time.Hour,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddOIDCSettings(tt.args.ctx, tt.args.oidcConfig)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeOIDCConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
oidcConfig *domain.OIDCSettings
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "oidc config not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOIDCSettingsAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
),
),
),
),
},
args: args{
ctx: context.Background(),
oidcConfig: &domain.OIDCSettings{
AccessTokenLifetime: 1 * time.Hour,
IdTokenLifetime: 1 * time.Hour,
RefreshTokenIdleExpiration: 1 * time.Hour,
RefreshTokenExpiration: 1 * time.Hour,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "secret generator change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewOIDCSettingsAddedEvent(
context.Background(),
&iam.NewAggregate().Aggregate,
time.Hour*1,
time.Hour*1,
time.Hour*1,
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newOIDCConfigChangedEvent(context.Background(),
time.Hour*2,
time.Hour*2,
time.Hour*2,
time.Hour*2),
),
},
),
),
},
args: args{
ctx: context.Background(),
oidcConfig: &domain.OIDCSettings{
AccessTokenLifetime: 2 * time.Hour,
IdTokenLifetime: 2 * time.Hour,
RefreshTokenIdleExpiration: 2 * time.Hour,
RefreshTokenExpiration: 2 * time.Hour,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeOIDCSettings(tt.args.ctx, tt.args.oidcConfig)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newOIDCConfigChangedEvent(ctx context.Context, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) *iam.OIDCSettingsChangedEvent {
changes := []iam.OIDCSettingsChanges{
iam.ChangeOIDCSettingsAccessTokenLifetime(accessTokenLifetime),
iam.ChangeOIDCSettingsIdTokenLifetime(idTokenLifetime),
iam.ChangeOIDCSettingsRefreshTokenIdleExpiration(refreshTokenIdleExpiration),
iam.ChangeOIDCSettingsRefreshTokenExpiration(refreshTokenExpiration),
}
event, _ := iam.NewOIDCSettingsChangeEvent(ctx,
&iam.NewAggregate().Aggregate,
changes,
)
return event
}