feat: App API v2 (#10077)

# Which Problems Are Solved

This PR *partially* addresses #9450 . Specifically, it implements the
resource based API for the apps. APIs for app keys ARE not part of this
PR.

# How the Problems Are Solved

- `CreateApplication`, `PatchApplication` (update) and
`RegenerateClientSecret` endpoints are now unique for all app types:
API, SAML and OIDC apps.
  - All new endpoints have integration tests
  - All new endpoints are using permission checks V2

# Additional Changes

- The `ListApplications` endpoint allows to do sorting (see protobuf for
details) and filtering by app type (see protobuf).
- SAML and OIDC update endpoint can now receive requests for partial
updates

# Additional Context

Partially addresses #9450
This commit is contained in:
Marco A.
2025-06-27 17:25:44 +02:00
committed by GitHub
parent 016676e1dc
commit 2691dae2b6
48 changed files with 6845 additions and 603 deletions

View File

@@ -85,3 +85,11 @@ func (c *Commands) checkPermissionDeleteProjectGrant(ctx context.Context, resour
}
return nil
}
func (c *Commands) checkPermissionUpdateApplication(ctx context.Context, resourceOwner, appID string) error {
return c.newPermissionCheck(ctx, domain.PermissionProjectAppWrite, project.AggregateType)(resourceOwner, appID)
}
func (c *Commands) checkPermissionDeleteApp(ctx context.Context, resourceOwner, appID string) error {
return c.newPermissionCheck(ctx, domain.PermissionProjectAppDelete, project.AggregateType)(resourceOwner, appID)
}

View File

@@ -15,7 +15,7 @@ type AddApp struct {
Name string
}
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
func (c *Commands) UpdateApplicationName(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
}
@@ -30,6 +30,13 @@ func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appC
if existingApp.Name == appChange.GetApplicationName() {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2m8vx", "Errors.NoChangesFound")
}
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
pushedEvents, err := c.eventstore.Push(
ctx,
@@ -59,6 +66,13 @@ func (c *Commands) DeactivateApplication(ctx context.Context, projectID, appID,
if existingApp.State != domain.AppStateActive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-dsh35", "Errors.Project.App.NotActive")
}
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationDeactivatedEvent(ctx, projectAgg, appID))
if err != nil {
@@ -86,6 +100,11 @@ func (c *Commands) ReactivateApplication(ctx context.Context, projectID, appID,
if existingApp.State != domain.AppStateInactive {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1n8cM", "Errors.Project.App.NotInactive")
}
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationReactivatedEvent(ctx, projectAgg, appID))
@@ -111,6 +130,13 @@ func (c *Commands) RemoveApplication(ctx context.Context, projectID, appID, reso
if existingApp.State == domain.AppStateUnspecified || existingApp.State == domain.AppStateRemoved {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-0po9s", "Errors.Project.App.NotExisting")
}
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
return nil, err
}
if err := c.checkPermissionDeleteApp(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
entityID := ""

View File

@@ -90,16 +90,24 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
}
if _, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
projectResOwner, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if resourceOwner == "" {
resourceOwner = projectResOwner
}
if !apiApp.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.App.Invalid")
}
appID, err := c.idGenerator.Next()
if err != nil {
return nil, err
appID := apiApp.AppID
if appID == "" {
appID, err = c.idGenerator.Next()
if err != nil {
return nil, err
}
}
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
@@ -112,6 +120,13 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
apiApp.AppID = appID
addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
events := []eventstore.Command{
@@ -150,7 +165,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
return result, nil
}
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
func (c *Commands) UpdateAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
if apiApp.AppID == "" || apiApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
}
@@ -165,6 +180,13 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
if !existingAPI.IsAPI() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
}
if err := c.eventstore.FilterToQueryReducer(ctx, existingAPI); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, existingAPI.ResourceOwner, existingAPI.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
ctx,
@@ -205,6 +227,11 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
if !existingAPI.IsAPI() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
}
if err := c.checkPermissionUpdateApplication(ctx, existingAPI.ResourceOwner, existingAPI.AggregateID); err != nil {
return nil, err
}
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
if err != nil {
return nil, err

View File

@@ -142,6 +142,7 @@ func TestAddAPIConfig(t *testing.T) {
}
func TestCommandSide_AddAPIApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
@@ -238,6 +239,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -292,6 +294,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -346,6 +349,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -390,6 +394,8 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
@@ -397,6 +403,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -413,6 +420,8 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
}
func TestCommandSide_ChangeAPIApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
@@ -516,6 +525,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
domain.APIAuthMethodTypePrivateKeyJWT),
),
),
expectFilter(),
),
},
args: args{
@@ -555,6 +565,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
domain.APIAuthMethodTypeBasic),
),
),
expectFilter(),
expectPush(
newAPIAppChangedEvent(context.Background(),
"app1",
@@ -593,14 +604,17 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
got, err := r.UpdateAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -615,6 +629,8 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
}
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
}
@@ -734,12 +750,15 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {

View File

@@ -5,6 +5,8 @@ import (
"strings"
"time"
"github.com/muhlemmer/gu"
http_util "github.com/zitadel/zitadel/internal/api/http"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
@@ -120,6 +122,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
}
}
// TODO: Combine with AddOIDCApplication and addOIDCApplicationWithID
func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
@@ -142,9 +145,15 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
if oidcApp == nil || oidcApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
}
if _, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
projectResOwner, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if resourceOwner == "" {
resourceOwner = projectResOwner
}
if oidcApp.AppName == "" || !oidcApp.IsValid() {
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid")
}
@@ -162,6 +171,13 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
defer func() { span.EndWithError(err) }()
addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
oidcApp.AppID = appID
@@ -183,27 +199,27 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
}
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
projectAgg,
oidcApp.OIDCVersion,
gu.Value(oidcApp.OIDCVersion),
oidcApp.AppID,
oidcApp.ClientID,
oidcApp.EncodedHash,
trimStringSliceWhiteSpaces(oidcApp.RedirectUris),
oidcApp.ResponseTypes,
oidcApp.GrantTypes,
oidcApp.ApplicationType,
oidcApp.AuthMethodType,
gu.Value(oidcApp.ApplicationType),
gu.Value(oidcApp.AuthMethodType),
trimStringSliceWhiteSpaces(oidcApp.PostLogoutRedirectUris),
oidcApp.DevMode,
oidcApp.AccessTokenType,
oidcApp.AccessTokenRoleAssertion,
oidcApp.IDTokenRoleAssertion,
oidcApp.IDTokenUserinfoAssertion,
oidcApp.ClockSkew,
gu.Value(oidcApp.DevMode),
gu.Value(oidcApp.AccessTokenType),
gu.Value(oidcApp.AccessTokenRoleAssertion),
gu.Value(oidcApp.IDTokenRoleAssertion),
gu.Value(oidcApp.IDTokenUserinfoAssertion),
gu.Value(oidcApp.ClockSkew),
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
oidcApp.SkipNativeAppSuccessPage,
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
oidcApp.LoginVersion,
strings.TrimSpace(oidcApp.LoginBaseURI),
gu.Value(oidcApp.SkipNativeAppSuccessPage),
strings.TrimSpace(gu.Value(oidcApp.BackChannelLogoutURI)),
gu.Value(oidcApp.LoginVersion),
strings.TrimSpace(gu.Value(oidcApp.LoginBaseURI)),
))
addedApplication.AppID = oidcApp.AppID
@@ -226,7 +242,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
return result, nil
}
func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
func (c *Commands) UpdateOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
if !oidc.IsValid() || oidc.AppID == "" || oidc.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5m9fs", "Errors.Project.App.OIDCConfigInvalid")
}
@@ -241,7 +257,23 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
if !existingOIDC.IsOIDC() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr34", "Errors.Project.App.IsNotOIDC")
}
if err := c.eventstore.FilterToQueryReducer(ctx, existingOIDC); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, existingOIDC.ResourceOwner, existingOIDC.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
var backChannelLogout, loginBaseURI *string
if oidc.BackChannelLogoutURI != nil {
backChannelLogout = gu.Ptr(strings.TrimSpace(*oidc.BackChannelLogoutURI))
}
if oidc.LoginBaseURI != nil {
loginBaseURI = gu.Ptr(strings.TrimSpace(*oidc.LoginBaseURI))
}
changedEvent, hasChanged, err := existingOIDC.NewChangedEvent(
ctx,
projectAgg,
@@ -261,9 +293,9 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
oidc.ClockSkew,
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
oidc.SkipNativeAppSuccessPage,
strings.TrimSpace(oidc.BackChannelLogoutURI),
backChannelLogout,
oidc.LoginVersion,
strings.TrimSpace(oidc.LoginBaseURI),
loginBaseURI,
)
if err != nil {
return nil, err
@@ -301,6 +333,11 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
if !existingOIDC.IsOIDC() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
}
if err := c.checkPermissionUpdateApplication(ctx, existingOIDC.ResourceOwner, existingOIDC.AggregateID); err != nil {
return nil, err
}
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
if err != nil {
return nil, err

View File

@@ -258,77 +258,77 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
postLogoutRedirectURIs []string,
responseTypes []domain.OIDCResponseType,
grantTypes []domain.OIDCGrantType,
appType domain.OIDCApplicationType,
authMethodType domain.OIDCAuthMethodType,
oidcVersion domain.OIDCVersion,
accessTokenType domain.OIDCTokenType,
appType *domain.OIDCApplicationType,
authMethodType *domain.OIDCAuthMethodType,
oidcVersion *domain.OIDCVersion,
accessTokenType *domain.OIDCTokenType,
devMode,
accessTokenRoleAssertion,
idTokenRoleAssertion,
idTokenUserinfoAssertion bool,
clockSkew time.Duration,
idTokenUserinfoAssertion *bool,
clockSkew *time.Duration,
additionalOrigins []string,
skipNativeAppSuccessPage bool,
backChannelLogoutURI string,
loginVersion domain.LoginVersion,
loginBaseURI string,
skipNativeAppSuccessPage *bool,
backChannelLogoutURI *string,
loginVersion *domain.LoginVersion,
loginBaseURI *string,
) (*project.OIDCConfigChangedEvent, bool, error) {
changes := make([]project.OIDCConfigChanges, 0)
var err error
if !slices.Equal(wm.RedirectUris, redirectURIS) {
if redirectURIS != nil && !slices.Equal(wm.RedirectUris, redirectURIS) {
changes = append(changes, project.ChangeRedirectURIs(redirectURIS))
}
if !slices.Equal(wm.ResponseTypes, responseTypes) {
if responseTypes != nil && !slices.Equal(wm.ResponseTypes, responseTypes) {
changes = append(changes, project.ChangeResponseTypes(responseTypes))
}
if !slices.Equal(wm.GrantTypes, grantTypes) {
if grantTypes != nil && !slices.Equal(wm.GrantTypes, grantTypes) {
changes = append(changes, project.ChangeGrantTypes(grantTypes))
}
if wm.ApplicationType != appType {
changes = append(changes, project.ChangeApplicationType(appType))
if appType != nil && wm.ApplicationType != *appType {
changes = append(changes, project.ChangeApplicationType(*appType))
}
if wm.AuthMethodType != authMethodType {
changes = append(changes, project.ChangeAuthMethodType(authMethodType))
if authMethodType != nil && wm.AuthMethodType != *authMethodType {
changes = append(changes, project.ChangeAuthMethodType(*authMethodType))
}
if !slices.Equal(wm.PostLogoutRedirectUris, postLogoutRedirectURIs) {
if postLogoutRedirectURIs != nil && !slices.Equal(wm.PostLogoutRedirectUris, postLogoutRedirectURIs) {
changes = append(changes, project.ChangePostLogoutRedirectURIs(postLogoutRedirectURIs))
}
if wm.OIDCVersion != oidcVersion {
changes = append(changes, project.ChangeVersion(oidcVersion))
if oidcVersion != nil && wm.OIDCVersion != *oidcVersion {
changes = append(changes, project.ChangeVersion(*oidcVersion))
}
if wm.DevMode != devMode {
changes = append(changes, project.ChangeDevMode(devMode))
if devMode != nil && wm.DevMode != *devMode {
changes = append(changes, project.ChangeDevMode(*devMode))
}
if wm.AccessTokenType != accessTokenType {
changes = append(changes, project.ChangeAccessTokenType(accessTokenType))
if accessTokenType != nil && wm.AccessTokenType != *accessTokenType {
changes = append(changes, project.ChangeAccessTokenType(*accessTokenType))
}
if wm.AccessTokenRoleAssertion != accessTokenRoleAssertion {
changes = append(changes, project.ChangeAccessTokenRoleAssertion(accessTokenRoleAssertion))
if accessTokenRoleAssertion != nil && wm.AccessTokenRoleAssertion != *accessTokenRoleAssertion {
changes = append(changes, project.ChangeAccessTokenRoleAssertion(*accessTokenRoleAssertion))
}
if wm.IDTokenRoleAssertion != idTokenRoleAssertion {
changes = append(changes, project.ChangeIDTokenRoleAssertion(idTokenRoleAssertion))
if idTokenRoleAssertion != nil && wm.IDTokenRoleAssertion != *idTokenRoleAssertion {
changes = append(changes, project.ChangeIDTokenRoleAssertion(*idTokenRoleAssertion))
}
if wm.IDTokenUserinfoAssertion != idTokenUserinfoAssertion {
changes = append(changes, project.ChangeIDTokenUserinfoAssertion(idTokenUserinfoAssertion))
if idTokenUserinfoAssertion != nil && wm.IDTokenUserinfoAssertion != *idTokenUserinfoAssertion {
changes = append(changes, project.ChangeIDTokenUserinfoAssertion(*idTokenUserinfoAssertion))
}
if wm.ClockSkew != clockSkew {
changes = append(changes, project.ChangeClockSkew(clockSkew))
if clockSkew != nil && wm.ClockSkew != *clockSkew {
changes = append(changes, project.ChangeClockSkew(*clockSkew))
}
if !slices.Equal(wm.AdditionalOrigins, additionalOrigins) {
if additionalOrigins != nil && !slices.Equal(wm.AdditionalOrigins, additionalOrigins) {
changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins))
}
if wm.SkipNativeAppSuccessPage != skipNativeAppSuccessPage {
changes = append(changes, project.ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage))
if skipNativeAppSuccessPage != nil && wm.SkipNativeAppSuccessPage != *skipNativeAppSuccessPage {
changes = append(changes, project.ChangeSkipNativeAppSuccessPage(*skipNativeAppSuccessPage))
}
if wm.BackChannelLogoutURI != backChannelLogoutURI {
changes = append(changes, project.ChangeBackChannelLogoutURI(backChannelLogoutURI))
if backChannelLogoutURI != nil && wm.BackChannelLogoutURI != *backChannelLogoutURI {
changes = append(changes, project.ChangeBackChannelLogoutURI(*backChannelLogoutURI))
}
if wm.LoginVersion != loginVersion {
changes = append(changes, project.ChangeOIDCLoginVersion(loginVersion))
if loginVersion != nil && wm.LoginVersion != *loginVersion {
changes = append(changes, project.ChangeOIDCLoginVersion(*loginVersion))
}
if wm.LoginBaseURI != loginBaseURI {
changes = append(changes, project.ChangeOIDCLoginBaseURI(loginBaseURI))
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
changes = append(changes, project.ChangeOIDCLoginBaseURI(*loginBaseURI))
}
if len(changes) == 0 {

View File

@@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/api/authz"
@@ -401,6 +402,8 @@ func TestAddOIDCApp(t *testing.T) {
}
func TestCommandSide_AddOIDCApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
@@ -497,6 +500,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -538,24 +542,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{" https://test.ch "},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{" https://test.ch/logout "},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{" https://sub.test.ch "},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: " https://test.ch/backchannel ",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: " https://login.test.ch ",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr(" https://test.ch/backchannel "),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr(" https://login.test.ch "),
},
resourceOwner: "org1",
},
@@ -569,24 +573,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AppName: "app",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://login.test.ch"),
State: domain.AppStateActive,
Compliance: &domain.Compliance{},
},
@@ -604,6 +608,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -645,24 +650,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AggregateID: "project1",
},
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://login.test.ch"),
},
resourceOwner: "org1",
},
@@ -676,24 +681,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
AppName: "app",
ClientID: "client1",
ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://login.test.ch"),
State: domain.AppStateActive,
Compliance: &domain.Compliance{},
},
@@ -702,6 +707,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
@@ -709,6 +715,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
checkPermission: newMockPermissionCheckAllowed(),
}
c.setMilestonesCompletedForTest("instanceID")
got, err := c.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
@@ -726,6 +733,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
}
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
}
@@ -775,7 +783,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
AggregateID: "project1",
},
AppID: "",
AuthMethodType: domain.OIDCAuthMethodTypePost,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
},
@@ -797,7 +805,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
AggregateID: "",
},
AppID: "appid",
AuthMethodType: domain.OIDCAuthMethodTypePost,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
},
@@ -821,7 +829,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
AggregateID: "project1",
},
AppID: "app1",
AuthMethodType: domain.OIDCAuthMethodTypePost,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
},
@@ -870,6 +878,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
),
),
),
expectFilter(),
),
},
args: args{
@@ -880,24 +889,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://login.test.ch"),
},
resourceOwner: "org1",
},
@@ -944,6 +953,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
),
),
),
expectFilter(),
),
},
args: args{
@@ -954,24 +964,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch "},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{" https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{" https://sub.test.ch "},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: " https://test.ch/backchannel ",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: " https://login.test.ch ",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr(" https://test.ch/backchannel "),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr(" https://login.test.ch "),
},
resourceOwner: "org1",
},
@@ -980,7 +990,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
},
},
{
name: "change oidc app, ok",
name: "partial change oidc app, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
@@ -1018,6 +1028,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
),
),
),
expectFilter(),
expectPush(
newOIDCAppChangedEvent(context.Background(),
"app1",
@@ -1032,26 +1043,11 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppID: "app1",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{" https://test-change.ch "},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{" https://test-change.ch/logout "},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeJWT,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2,
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
AppID: "app1",
AppName: "app",
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
},
resourceOwner: "org1",
},
@@ -1064,24 +1060,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
AppID: "app1",
ClientID: "client1@project",
AppName: "app",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
RedirectUris: []string{"https://test-change.ch"},
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
PostLogoutRedirectUris: []string{"https://test-change.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeJWT,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: time.Second * 2,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: gu.Ptr(false),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: true,
BackChannelLogoutURI: "https://test.ch/backchannel",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://login.test.ch",
SkipNativeAppSuccessPage: gu.Ptr(true),
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
LoginVersion: gu.Ptr(domain.LoginVersion1),
LoginBaseURI: gu.Ptr(""),
Compliance: &domain.Compliance{},
State: domain.AppStateActive,
},
@@ -1090,10 +1086,12 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
eventstore: tt.fields.eventstore(t),
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
got, err := r.UpdateOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -1108,6 +1106,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
}
func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(*testing.T) *eventstore.Eventstore
}
@@ -1237,36 +1237,40 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
AppName: "app",
ClientID: "client1@project",
ClientSecretString: "secret",
AuthMethodType: domain.OIDCAuthMethodTypePost,
OIDCVersion: domain.OIDCVersionV1,
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
RedirectUris: []string{"https://test.ch"},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeWeb,
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
DevMode: true,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: true,
IDTokenRoleAssertion: true,
IDTokenUserinfoAssertion: true,
ClockSkew: time.Second * 1,
DevMode: gu.Ptr(true),
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
AccessTokenRoleAssertion: gu.Ptr(true),
IDTokenRoleAssertion: gu.Ptr(true),
IDTokenUserinfoAssertion: gu.Ptr(true),
ClockSkew: gu.Ptr(time.Second * 1),
AdditionalOrigins: []string{"https://sub.test.ch"},
SkipNativeAppSuccessPage: false,
BackChannelLogoutURI: "",
LoginVersion: domain.LoginVersionUnspecified,
SkipNativeAppSuccessPage: gu.Ptr(false),
BackChannelLogoutURI: gu.Ptr(""),
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
LoginBaseURI: gu.Ptr(""),
State: domain.AppStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(*testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
newHashedSecret: mockHashedSecret("secret"),
defaultSecretGenerators: &SecretGenerators{
ClientSecret: emptyConfig,
},
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -1284,16 +1288,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
changes := []project.OIDCConfigChanges{
project.ChangeRedirectURIs([]string{"https://test-change.ch"}),
project.ChangePostLogoutRedirectURIs([]string{"https://test-change.ch/logout"}),
project.ChangeDevMode(true),
project.ChangeAccessTokenType(domain.OIDCTokenTypeJWT),
project.ChangeAccessTokenRoleAssertion(false),
project.ChangeIDTokenRoleAssertion(false),
project.ChangeIDTokenUserinfoAssertion(false),
project.ChangeClockSkew(time.Second * 2),
project.ChangeOIDCLoginVersion(domain.LoginVersion2),
project.ChangeOIDCLoginBaseURI("https://login.test.ch"),
project.ChangeAuthMethodType(domain.OIDCAuthMethodTypeBasic),
}
event, _ := project.NewOIDCConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,

View File

@@ -3,6 +3,7 @@ package command
import (
"context"
"github.com/muhlemmer/gu"
"github.com/zitadel/saml/pkg/provider/xml"
"github.com/zitadel/zitadel/internal/domain"
@@ -16,10 +17,22 @@ func (c *Commands) AddSAMLApplication(ctx context.Context, application *domain.S
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-35Fn0", "Errors.Project.App.Invalid")
}
if _, err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner); err != nil {
projectResOwner, err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if resourceOwner == "" {
resourceOwner = projectResOwner
}
addedApplication := NewSAMLApplicationWriteModel(application.AggregateID, resourceOwner)
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
return nil, err
}
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
events, err := c.addSAMLApplication(ctx, projectAgg, application)
if err != nil {
@@ -49,12 +62,8 @@ func (c *Commands) addSAMLApplication(ctx context.Context, projectAgg *eventstor
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n9df", "Errors.Project.App.Invalid")
}
if samlApp.Metadata == nil && samlApp.MetadataURL == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SAML-podix9", "Errors.Project.App.SAMLMetadataMissing")
}
if samlApp.MetadataURL != "" {
data, err := xml.ReadMetadataFromURL(c.httpClient, samlApp.MetadataURL)
if samlApp.MetadataURL != nil && *samlApp.MetadataURL != "" {
data, err := xml.ReadMetadataFromURL(c.httpClient, *samlApp.MetadataURL)
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "SAML-wmqlo1", "Errors.Project.App.SAMLMetadataMissing")
}
@@ -78,14 +87,14 @@ func (c *Commands) addSAMLApplication(ctx context.Context, projectAgg *eventstor
samlApp.AppID,
string(entity.EntityID),
samlApp.Metadata,
samlApp.MetadataURL,
samlApp.LoginVersion,
samlApp.LoginBaseURI,
gu.Value(samlApp.MetadataURL),
gu.Value(samlApp.LoginVersion),
gu.Value(samlApp.LoginBaseURI),
),
}, nil
}
func (c *Commands) ChangeSAMLApplication(ctx context.Context, samlApp *domain.SAMLApp, resourceOwner string) (*domain.SAMLApp, error) {
func (c *Commands) UpdateSAMLApplication(ctx context.Context, samlApp *domain.SAMLApp, resourceOwner string) (*domain.SAMLApp, error) {
if !samlApp.IsValid() || samlApp.AppID == "" || samlApp.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5n9fs", "Errors.Project.App.SAMLConfigInvalid")
}
@@ -100,10 +109,15 @@ func (c *Commands) ChangeSAMLApplication(ctx context.Context, samlApp *domain.SA
if !existingSAML.IsSAML() {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr35", "Errors.Project.App.IsNotSAML")
}
if err := c.checkPermissionUpdateApplication(ctx, existingSAML.ResourceOwner, existingSAML.AggregateID); err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingSAML.WriteModel)
if samlApp.MetadataURL != "" {
data, err := xml.ReadMetadataFromURL(c.httpClient, samlApp.MetadataURL)
if samlApp.MetadataURL != nil && *samlApp.MetadataURL != "" {
data, err := xml.ReadMetadataFromURL(c.httpClient, *samlApp.MetadataURL)
if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "SAML-J3kg3", "Errors.Project.App.SAMLMetadataMissing")
}

View File

@@ -2,7 +2,7 @@ package command
import (
"context"
"reflect"
"slices"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -170,26 +170,26 @@ func (wm *SAMLApplicationWriteModel) NewChangedEvent(
appID string,
entityID string,
metadata []byte,
metadataURL string,
loginVersion domain.LoginVersion,
loginBaseURI string,
metadataURL *string,
loginVersion *domain.LoginVersion,
loginBaseURI *string,
) (*project.SAMLConfigChangedEvent, bool, error) {
changes := make([]project.SAMLConfigChanges, 0)
var err error
if !reflect.DeepEqual(wm.Metadata, metadata) {
if metadata != nil && !slices.Equal(wm.Metadata, metadata) {
changes = append(changes, project.ChangeMetadata(metadata))
}
if wm.MetadataURL != metadataURL {
changes = append(changes, project.ChangeMetadataURL(metadataURL))
if metadataURL != nil && wm.MetadataURL != *metadataURL {
changes = append(changes, project.ChangeMetadataURL(*metadataURL))
}
if wm.EntityID != entityID {
changes = append(changes, project.ChangeEntityID(entityID))
}
if wm.LoginVersion != loginVersion {
changes = append(changes, project.ChangeSAMLLoginVersion(loginVersion))
if loginVersion != nil && wm.LoginVersion != *loginVersion {
changes = append(changes, project.ChangeSAMLLoginVersion(*loginVersion))
}
if wm.LoginBaseURI != loginBaseURI {
changes = append(changes, project.ChangeSAMLLoginBaseURI(loginBaseURI))
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
changes = append(changes, project.ChangeSAMLLoginBaseURI(*loginBaseURI))
}
if len(changes) == 0 {

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"testing"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/api/authz"
@@ -49,6 +50,8 @@ var testMetadataChangedEntityID = []byte(`<?xml version="1.0"?>
`)
func TestCommandSide_AddSAMLApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
idGenerator id.Generator
@@ -117,6 +120,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
),
},
args: args{
@@ -134,6 +138,37 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "empty metas, invalid argument error",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
project.NewProjectAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"project", true, true, true,
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
samlApp: &domain.SAMLApp{
ObjectRoot: models.ObjectRoot{
AggregateID: "project1",
},
AppName: "app",
EntityID: "https://test.com/saml/metadata",
},
resourceOwner: "org1",
},
res: res{
err: zerrors.IsErrorInvalidArgument,
},
},
{
name: "create saml app, metadata not parsable",
fields: fields{
@@ -146,6 +181,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
},
@@ -158,7 +194,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: []byte("test metadata"),
MetadataURL: "",
MetadataURL: gu.Ptr(""),
},
resourceOwner: "org1",
},
@@ -178,6 +214,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -206,7 +243,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
MetadataURL: gu.Ptr(""),
},
resourceOwner: "org1",
},
@@ -216,12 +253,14 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
State: domain.AppStateActive,
AppID: "app1",
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: gu.Ptr(""),
State: domain.AppStateActive,
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
LoginBaseURI: gu.Ptr(""),
},
},
},
@@ -237,6 +276,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -265,9 +305,9 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
MetadataURL: gu.Ptr(""),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://test.com/login"),
},
resourceOwner: "org1",
},
@@ -281,10 +321,10 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
MetadataURL: gu.Ptr(""),
State: domain.AppStateActive,
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://test.com/login"),
},
},
},
@@ -300,6 +340,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
expectPush(
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -329,7 +370,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: nil,
MetadataURL: "http://localhost:8080/saml/metadata",
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
},
resourceOwner: "org1",
},
@@ -339,12 +380,14 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "http://localhost:8080/saml/metadata",
State: domain.AppStateActive,
AppID: "app1",
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
State: domain.AppStateActive,
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
LoginBaseURI: gu.Ptr(""),
},
},
},
@@ -360,6 +403,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
domain.PrivateLabelingSettingUnspecified),
),
),
expectFilter(),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
httpClient: newTestClient(http.StatusNotFound, nil),
@@ -373,7 +417,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test.com/saml/metadata",
Metadata: nil,
MetadataURL: "http://localhost:8080/saml/metadata",
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
},
resourceOwner: "org1",
},
@@ -385,10 +429,13 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := &Commands{
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
httpClient: tt.fields.httpClient,
eventstore: tt.fields.eventstore(t),
idGenerator: tt.fields.idGenerator,
httpClient: tt.fields.httpClient,
checkPermission: newMockPermissionCheckAllowed(),
}
c.setMilestonesCompletedForTest("instanceID")
got, err := c.AddSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
@@ -406,6 +453,8 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
}
func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
httpClient *http.Client
@@ -544,7 +593,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppID: "app1",
EntityID: "https://test.com/saml/metadata",
Metadata: nil,
MetadataURL: "http://localhost:8080/saml/metadata",
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
},
resourceOwner: "org1",
},
@@ -590,7 +639,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppID: "app1",
EntityID: "https://test.com/saml/metadata",
Metadata: testMetadata,
MetadataURL: "",
MetadataURL: gu.Ptr(""),
},
resourceOwner: "org1",
},
@@ -646,7 +695,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: nil,
MetadataURL: "http://localhost:8080/saml/metadata",
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
},
resourceOwner: "org1",
},
@@ -656,17 +705,19 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "http://localhost:8080/saml/metadata",
State: domain.AppStateActive,
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
State: domain.AppStateActive,
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
LoginBaseURI: gu.Ptr(""),
},
},
},
{
name: "change saml app, ok, metadata",
name: "partial change saml app, ok, metadata",
fields: fields{
eventstore: expectEventstore(
expectFilter(
@@ -713,7 +764,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
MetadataURL: gu.Ptr(""),
},
resourceOwner: "org1",
},
@@ -723,15 +774,18 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AggregateID: "project1",
ResourceOwner: "org1",
},
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
State: domain.AppStateActive,
AppID: "app1",
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: gu.Ptr(""),
State: domain.AppStateActive,
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
LoginBaseURI: gu.Ptr(""),
},
},
}, {
},
{
name: "change saml app, ok, loginversion",
fields: fields{
eventstore: expectEventstore(
@@ -781,9 +835,9 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
MetadataURL: gu.Ptr(""),
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://test.com/login"),
},
resourceOwner: "org1",
},
@@ -797,10 +851,10 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
AppName: "app",
EntityID: "https://test2.com/saml/metadata",
Metadata: testMetadataChangedEntityID,
MetadataURL: "",
MetadataURL: gu.Ptr(""),
State: domain.AppStateActive,
LoginVersion: domain.LoginVersion2,
LoginBaseURI: "https://test.com/login",
LoginVersion: gu.Ptr(domain.LoginVersion2),
LoginBaseURI: gu.Ptr("https://test.com/login"),
},
},
},
@@ -808,11 +862,14 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore(t),
httpClient: tt.fields.httpClient,
eventstore: tt.fields.eventstore(t),
httpClient: tt.fields.httpClient,
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
got, err := r.UpdateSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@@ -8,13 +8,16 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository/mock"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/zerrors"
)
func TestCommandSide_ChangeApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -35,9 +38,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "invalid app missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -55,9 +56,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "invalid app missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -74,9 +73,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "invalid app missing name, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -94,10 +91,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
eventstore: expectEventstore(expectFilter()),
},
args: args{
ctx: context.Background(),
@@ -115,8 +109,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "app name not changed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -142,8 +135,14 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
{
name: "app changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -179,10 +178,13 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ChangeApplication(tt.args.ctx, tt.args.projectID, tt.args.app, tt.args.resourceOwner)
got, err := r.UpdateApplicationName(tt.args.ctx, tt.args.projectID, tt.args.app, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -197,8 +199,10 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
}
func TestCommandSide_DeactivateApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -219,9 +223,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -236,9 +238,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -253,8 +253,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(),
),
},
@@ -271,8 +270,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
{
name: "app already inactive, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -299,8 +297,14 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
{
name: "app deactivate, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"app1",
"app",
)),
),
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -331,8 +335,11 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.DeactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -349,8 +356,10 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
}
func TestCommandSide_ReactivateApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -371,9 +380,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -388,9 +395,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -405,10 +410,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
eventstore: expectEventstore(expectFilter()),
},
args: args{
ctx: context.Background(),
@@ -423,8 +425,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
{
name: "app already active, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -447,8 +448,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
{
name: "app reactivate, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -483,8 +483,11 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.ReactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {
@@ -501,8 +504,10 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
}
func TestCommandSide_RemoveApplication(t *testing.T) {
t.Parallel()
type fields struct {
eventstore *eventstore.Eventstore
eventstore func(*testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
@@ -523,9 +528,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
{
name: "missing projectid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -540,9 +543,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
{
name: "missing appid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
},
args: args{
ctx: context.Background(),
@@ -557,10 +558,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
{
name: "app not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
eventstore: expectEventstore(expectFilter()),
},
args: args{
ctx: context.Background(),
@@ -575,8 +573,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
{
name: "app remove, entityID, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -584,6 +581,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
"app",
)),
),
expectFilter(),
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -625,8 +623,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
{
name: "app remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -636,6 +633,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
),
// app is not saml, or no saml config available
expectFilter(),
expectFilter(),
expectPush(
project.NewApplicationRemovedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
@@ -661,8 +659,11 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
r := &Commands{
eventstore: tt.fields.eventstore,
eventstore: tt.fields.eventstore(t),
checkPermission: newMockPermissionCheckAllowed(),
}
got, err := r.RemoveApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
if tt.res.err == nil {

View File

@@ -1,6 +1,8 @@
package command
import (
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel/internal/domain"
)
@@ -35,21 +37,21 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
RedirectUris: writeModel.RedirectUris,
ResponseTypes: writeModel.ResponseTypes,
GrantTypes: writeModel.GrantTypes,
ApplicationType: writeModel.ApplicationType,
AuthMethodType: writeModel.AuthMethodType,
ApplicationType: gu.Ptr(writeModel.ApplicationType),
AuthMethodType: gu.Ptr(writeModel.AuthMethodType),
PostLogoutRedirectUris: writeModel.PostLogoutRedirectUris,
OIDCVersion: writeModel.OIDCVersion,
DevMode: writeModel.DevMode,
AccessTokenType: writeModel.AccessTokenType,
AccessTokenRoleAssertion: writeModel.AccessTokenRoleAssertion,
IDTokenRoleAssertion: writeModel.IDTokenRoleAssertion,
IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion,
ClockSkew: writeModel.ClockSkew,
OIDCVersion: gu.Ptr(writeModel.OIDCVersion),
DevMode: gu.Ptr(writeModel.DevMode),
AccessTokenType: gu.Ptr(writeModel.AccessTokenType),
AccessTokenRoleAssertion: gu.Ptr(writeModel.AccessTokenRoleAssertion),
IDTokenRoleAssertion: gu.Ptr(writeModel.IDTokenRoleAssertion),
IDTokenUserinfoAssertion: gu.Ptr(writeModel.IDTokenUserinfoAssertion),
ClockSkew: gu.Ptr(writeModel.ClockSkew),
AdditionalOrigins: writeModel.AdditionalOrigins,
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
BackChannelLogoutURI: writeModel.BackChannelLogoutURI,
LoginVersion: writeModel.LoginVersion,
LoginBaseURI: writeModel.LoginBaseURI,
SkipNativeAppSuccessPage: gu.Ptr(writeModel.SkipNativeAppSuccessPage),
BackChannelLogoutURI: gu.Ptr(writeModel.BackChannelLogoutURI),
LoginVersion: gu.Ptr(writeModel.LoginVersion),
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
}
}
@@ -60,10 +62,10 @@ func samlWriteModelToSAMLConfig(writeModel *SAMLApplicationWriteModel) *domain.S
AppName: writeModel.AppName,
State: writeModel.State,
Metadata: writeModel.Metadata,
MetadataURL: writeModel.MetadataURL,
MetadataURL: gu.Ptr(writeModel.MetadataURL),
EntityID: writeModel.EntityID,
LoginVersion: writeModel.LoginVersion,
LoginBaseURI: writeModel.LoginBaseURI,
LoginVersion: gu.Ptr(writeModel.LoginVersion),
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
}
}
@@ -78,15 +80,6 @@ func apiWriteModelToAPIConfig(writeModel *APIApplicationWriteModel) *domain.APIA
}
}
func roleWriteModelToRole(writeModel *ProjectRoleWriteModel) *domain.ProjectRole {
return &domain.ProjectRole{
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
Key: writeModel.Key,
DisplayName: writeModel.DisplayName,
Group: writeModel.Group,
}
}
func memberWriteModelToProjectGrantMember(writeModel *ProjectGrantMemberWriteModel) *domain.ProjectGrantMember {
return &domain.ProjectGrantMember{
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),

View File

@@ -2,6 +2,7 @@ package command
import (
"context"
"slices"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -120,7 +121,7 @@ func (wm *ProjectWriteModel) NewChangedEvent(
}
func isProjectStateExists(state domain.ProjectState) bool {
return !hasProjectState(state, domain.ProjectStateRemoved, domain.ProjectStateUnspecified)
return !slices.Contains([]domain.ProjectState{domain.ProjectStateRemoved, domain.ProjectStateUnspecified}, state)
}
func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
@@ -130,12 +131,3 @@ func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggre
func ProjectAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
return project.AggregateFromWriteModel(ctx, wm)
}
func hasProjectState(check domain.ProjectState, states ...domain.ProjectState) bool {
for _, state := range states {
if check == state {
return true
}
}
return false
}