mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-06 06:12:16 +00:00
feat: Feature flag for relational tables (#10599)
# Which Problems Are Solved This PR introduces a new feature flag `EnableRelationalTables` that will be used in following implementations to decide whether Zitadel should use the relational model or the event sourcing one. # TODO - [x] Implement flag at system level - [x] Display the flag on console: https://github.com/zitadel/zitadel/pull/10615 # How the Problems Are Solved - Implement loading the flag from config - Add persistence of the flag through gRPC endpoint (SetInstanceFeatures) - Implement reading of the flag through gRPC endpoint (GetInstanceFeatures) # Additional Changes Some minor refactoring to remove un-needed generics annotations # Additional Context - Closes #10574 --------- Co-authored-by: Silvan <[email protected]>
This commit is contained in:
@@ -26,6 +26,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) (*command
|
||||
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
||||
LoginV2: loginV2,
|
||||
PermissionCheckV2: req.PermissionCheckV2,
|
||||
EnableRelationalTables: req.EnableRelationalTables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -40,6 +41,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
||||
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||
PermissionCheckV2: featureSourceToFlagPb(&f.PermissionCheckV2),
|
||||
EnableRelationalTables: featureSourceToFlagPb(&f.EnableRelationalTables),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +61,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) (*com
|
||||
LoginV2: loginV2,
|
||||
PermissionCheckV2: req.PermissionCheckV2,
|
||||
ConsoleUseV2UserApi: req.ConsoleUseV2UserApi,
|
||||
EnableRelationalTables: req.EnableRelationalTables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -75,6 +78,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||
PermissionCheckV2: featureSourceToFlagPb(&f.PermissionCheckV2),
|
||||
ConsoleUseV2UserApi: featureSourceToFlagPb(&f.ConsoleUseV2UserApi),
|
||||
EnableRelationalTables: featureSourceToFlagPb(&f.EnableRelationalTables),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
)
|
||||
|
||||
func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
arg := &feature_pb.SetSystemFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
@@ -28,6 +30,9 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
},
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -39,13 +44,23 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
}
|
||||
|
||||
// Test
|
||||
got, err := systemFeaturesToCommand(arg)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, want, got)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_systemFeaturesToPb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
arg := &query.SystemFeatures{
|
||||
Details: &domain.ObjectDetails{
|
||||
Sequence: 22,
|
||||
@@ -87,6 +102,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
EnableRelationalTables: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -127,12 +146,22 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
EnableRelationalTables: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
|
||||
// Test
|
||||
got := systemFeaturesToPb(arg)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Given
|
||||
arg := &feature_pb.SetInstanceFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
@@ -145,7 +174,9 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
},
|
||||
ConsoleUseV2UserApi: gu.Ptr(true),
|
||||
ConsoleUseV2UserApi: gu.Ptr(true),
|
||||
PermissionCheckV2: gu.Ptr(false),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -159,14 +190,22 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
ConsoleUseV2UserApi: gu.Ptr(true),
|
||||
ConsoleUseV2UserApi: gu.Ptr(true),
|
||||
PermissionCheckV2: gu.Ptr(false),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
}
|
||||
|
||||
// Test
|
||||
got, err := instanceFeaturesToCommand(arg)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, want, got)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
arg := &query.InstanceFeatures{
|
||||
Details: &domain.ObjectDetails{
|
||||
Sequence: 22,
|
||||
@@ -212,6 +251,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
EnableRelationalTables: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -260,6 +303,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
EnableRelationalTables: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
||||
@@ -76,7 +76,8 @@ func TestServer_SetSystemFeatures(t *testing.T) {
|
||||
args: args{
|
||||
ctx: SystemCTX,
|
||||
req: &feature.SetSystemFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
want: &feature.SetSystemFeaturesResponse{
|
||||
@@ -170,8 +171,9 @@ func TestServer_GetSystemFeatures(t *testing.T) {
|
||||
name: "some features",
|
||||
prepare: func(t *testing.T) {
|
||||
_, err := Client.SetSystemFeatures(SystemCTX, &feature.SetSystemFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(false),
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(false),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
@@ -188,6 +190,10 @@ func TestServer_GetSystemFeatures(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
EnableRelationalTables: &feature.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -209,6 +215,7 @@ func TestServer_GetSystemFeatures(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
|
||||
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
|
||||
assertFeatureFlag(t, tt.want.EnableRelationalTables, got.EnableRelationalTables)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -247,7 +254,8 @@ func TestServer_SetInstanceFeatures(t *testing.T) {
|
||||
args: args{
|
||||
ctx: IamCTX,
|
||||
req: &feature.SetInstanceFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
want: &feature.SetInstanceFeaturesResponse{
|
||||
@@ -363,14 +371,19 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
EnableRelationalTables: &feature.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "some features, no inheritance",
|
||||
prepare: func(t *testing.T) {
|
||||
_, err := Client.SetInstanceFeatures(IamCTX, &feature.SetInstanceFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
@@ -387,6 +400,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
EnableRelationalTables: &feature.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -412,6 +429,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
EnableRelationalTables: &feature.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -433,6 +454,7 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
|
||||
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
|
||||
assertFeatureFlag(t, tt.want.EnableRelationalTables, got.EnableRelationalTables)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +141,7 @@ type InstanceSetupFeatures struct {
|
||||
LoginV2 *InstanceSetupFeatureLoginV2
|
||||
PermissionCheckV2 *bool
|
||||
ConsoleUseV2UserApi *bool
|
||||
EnableRelationalTables *bool
|
||||
}
|
||||
|
||||
type InstanceSetupFeatureLoginV2 struct {
|
||||
@@ -174,6 +175,7 @@ func (f *InstanceSetupFeatures) ToInstanceFeatures() (_ *InstanceFeatures, err e
|
||||
LoginV2: loginV2,
|
||||
PermissionCheckV2: f.PermissionCheckV2,
|
||||
ConsoleUseV2UserApi: f.ConsoleUseV2UserApi,
|
||||
EnableRelationalTables: f.EnableRelationalTables,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@ type InstanceFeatures struct {
|
||||
LoginV2 *feature.LoginV2
|
||||
PermissionCheckV2 *bool
|
||||
ConsoleUseV2UserApi *bool
|
||||
EnableRelationalTables *bool
|
||||
}
|
||||
|
||||
func (m *InstanceFeatures) isEmpty() bool {
|
||||
return m.LoginDefaultOrg == nil &&
|
||||
return m == nil || (m.LoginDefaultOrg == nil &&
|
||||
m.UserSchema == nil &&
|
||||
m.TokenExchange == nil &&
|
||||
// nil check to allow unset improvements
|
||||
@@ -35,7 +36,9 @@ func (m *InstanceFeatures) isEmpty() bool {
|
||||
m.OIDCSingleV1SessionTermination == nil &&
|
||||
m.EnableBackChannelLogout == nil &&
|
||||
m.LoginV2 == nil &&
|
||||
m.PermissionCheckV2 == nil && m.ConsoleUseV2UserApi == nil
|
||||
m.PermissionCheckV2 == nil &&
|
||||
m.ConsoleUseV2UserApi == nil &&
|
||||
m.EnableRelationalTables == nil)
|
||||
}
|
||||
|
||||
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
||||
|
||||
@@ -76,6 +76,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceLoginVersion,
|
||||
feature_v2.InstancePermissionCheckV2,
|
||||
feature_v2.InstanceConsoleUseV2UserApi,
|
||||
feature_v2.InstanceEnableRelationalTables,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -117,6 +118,9 @@ func reduceInstanceFeature(features *InstanceFeatures, key feature.Key, value an
|
||||
case feature.KeyConsoleUseV2UserApi:
|
||||
v := value.(bool)
|
||||
features.ConsoleUseV2UserApi = &v
|
||||
case feature.KeyEnableRelationalTables:
|
||||
v := value.(bool)
|
||||
features.EnableRelationalTables = &v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,5 +137,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.InstanceLoginVersion)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.InstancePermissionCheckV2)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.ConsoleUseV2UserApi, f.ConsoleUseV2UserApi, feature_v2.InstanceConsoleUseV2UserApi)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.InstanceEnableRelationalTables)
|
||||
return cmds
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
)
|
||||
|
||||
func Test_reduceInstanceFeature(t *testing.T) {
|
||||
type args struct {
|
||||
features *InstanceFeatures
|
||||
key feature.Key
|
||||
value any
|
||||
}
|
||||
tt := []struct {
|
||||
name string
|
||||
args args
|
||||
expected *InstanceFeatures
|
||||
}{
|
||||
{
|
||||
name: "key unspecified",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyUnspecified,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{},
|
||||
},
|
||||
{
|
||||
name: "login default org",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyLoginDefaultOrg,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{LoginDefaultOrg: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "token exchange",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyTokenExchange,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{TokenExchange: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "user schema",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyUserSchema,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{UserSchema: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "improved performance",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyImprovedPerformance,
|
||||
value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject},
|
||||
},
|
||||
expected: &InstanceFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeProject}},
|
||||
},
|
||||
{
|
||||
name: "debug OIDC parent error",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyDebugOIDCParentError,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{DebugOIDCParentError: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "OIDC single v1 session termination",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyOIDCSingleV1SessionTermination,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "enable back channel logout",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyEnableBackChannelLogout,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{EnableBackChannelLogout: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "login v2",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyLoginV2,
|
||||
value: &feature.LoginV2{},
|
||||
},
|
||||
expected: &InstanceFeatures{LoginV2: &feature.LoginV2{}},
|
||||
},
|
||||
{
|
||||
name: "permission check v2",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyPermissionCheckV2,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{PermissionCheckV2: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "console use v2 user api",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyConsoleUseV2UserApi,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{ConsoleUseV2UserApi: gu.Ptr(true)},
|
||||
},
|
||||
{
|
||||
name: "enable relational tables",
|
||||
args: args{
|
||||
features: &InstanceFeatures{},
|
||||
key: feature.KeyEnableRelationalTables,
|
||||
value: true,
|
||||
},
|
||||
expected: &InstanceFeatures{EnableRelationalTables: gu.Ptr(true)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test
|
||||
reduceInstanceFeature(tc.args.features, tc.args.key, tc.args.value)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expected, tc.args.features)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,15 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
feature_v1 "github.com/zitadel/zitadel/internal/repository/feature"
|
||||
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := authz.WithInstanceID(context.Background(), "instance1")
|
||||
aggregate := feature_v2.NewAggregate("instance1", "instance1")
|
||||
|
||||
@@ -53,7 +56,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
),
|
||||
@@ -70,7 +73,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
name: "set LoginDefaultOrg, update from v1",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v1.NewSetEvent[feature_v1.Boolean](
|
||||
eventFromEventPusher(feature_v1.NewSetEvent(
|
||||
ctx, &eventstore.Aggregate{
|
||||
ID: "instance1",
|
||||
ResourceOwner: "instance1",
|
||||
@@ -82,7 +85,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
),
|
||||
@@ -100,7 +103,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, true,
|
||||
),
|
||||
@@ -118,7 +121,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPushFailed(io.ErrClosedPipe,
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceConsoleUseV2UserApi, true,
|
||||
),
|
||||
@@ -134,24 +137,27 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent(ctx, aggregate,
|
||||
feature_v2.InstanceEnableRelationalTables, true),
|
||||
),
|
||||
),
|
||||
args: args{ctx, &InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
@@ -162,7 +168,7 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
// throw in some set events, reset and set again.
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -170,17 +176,17 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.InstanceOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
),
|
||||
@@ -197,6 +203,8 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := &Commands{
|
||||
eventstore: tt.eventstore(t),
|
||||
}
|
||||
@@ -227,7 +235,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
|
||||
name: "push error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -242,7 +250,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
|
||||
name: "success",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -259,7 +267,7 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
|
||||
name: "no change after previous reset",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -294,3 +302,71 @@ func TestCommands_ResetInstanceFeatures(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceFeatures_isEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
features *InstanceFeatures
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "nil features",
|
||||
features: nil,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "empty features",
|
||||
features: &InstanceFeatures{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "LoginDefaultOrg set",
|
||||
features: &InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "UserSchema set",
|
||||
features: &InstanceFeatures{
|
||||
UserSchema: gu.Ptr(true),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "TokenExchange set",
|
||||
features: &InstanceFeatures{
|
||||
TokenExchange: gu.Ptr(true),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "ImprovedPerformance set",
|
||||
features: &InstanceFeatures{
|
||||
ImprovedPerformance: []feature.ImprovedPerformanceType{},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "multiple fields set",
|
||||
features: &InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(false),
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := tc.features.isEmpty()
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ package command
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
@@ -18,6 +21,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
id_mock "github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@@ -1727,3 +1731,126 @@ func TestCommandSide_RemoveInstance(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceSetupFeatures_ToInstanceFeatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type fields struct {
|
||||
LoginDefaultOrg *bool
|
||||
UserSchema *bool
|
||||
TokenExchange *bool
|
||||
ImprovedPerformance []feature.ImprovedPerformanceType
|
||||
DebugOIDCParentError *bool
|
||||
OIDCSingleV1SessionTermination *bool
|
||||
EnableBackChannelLogout *bool
|
||||
LoginV2 *InstanceSetupFeatureLoginV2
|
||||
PermissionCheckV2 *bool
|
||||
ConsoleUseV2UserApi *bool
|
||||
EnableRelationalTables *bool
|
||||
}
|
||||
|
||||
correctlyParsedURI, err := url.Parse("https://example.com")
|
||||
require.NoError(t, err)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want *InstanceFeatures
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "nil features returns nil",
|
||||
fields: fields{},
|
||||
want: &InstanceFeatures{},
|
||||
},
|
||||
{
|
||||
name: "all fields no login v2",
|
||||
fields: fields{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(false),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
ConsoleUseV2UserApi: gu.Ptr(false),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
},
|
||||
want: &InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(false),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgDomainVerified},
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(false),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
LoginV2: nil,
|
||||
PermissionCheckV2: gu.Ptr(true),
|
||||
ConsoleUseV2UserApi: gu.Ptr(false),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with login v2 no base uri",
|
||||
fields: fields{
|
||||
LoginV2: &InstanceSetupFeatureLoginV2{
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
want: &InstanceFeatures{
|
||||
LoginV2: &feature.LoginV2{
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with login v2 valid base uri",
|
||||
fields: fields{
|
||||
LoginV2: &InstanceSetupFeatureLoginV2{
|
||||
Required: true,
|
||||
BaseURI: gu.Ptr("https://example.com"),
|
||||
},
|
||||
},
|
||||
want: &InstanceFeatures{
|
||||
LoginV2: &feature.LoginV2{
|
||||
Required: true,
|
||||
BaseURI: correctlyParsedURI,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with login v2 invalid base uri",
|
||||
fields: fields{
|
||||
LoginV2: &InstanceSetupFeatureLoginV2{
|
||||
Required: true,
|
||||
BaseURI: gu.Ptr("://invalid"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
f := &InstanceSetupFeatures{
|
||||
LoginDefaultOrg: tc.fields.LoginDefaultOrg,
|
||||
UserSchema: tc.fields.UserSchema,
|
||||
TokenExchange: tc.fields.TokenExchange,
|
||||
ImprovedPerformance: tc.fields.ImprovedPerformance,
|
||||
DebugOIDCParentError: tc.fields.DebugOIDCParentError,
|
||||
OIDCSingleV1SessionTermination: tc.fields.OIDCSingleV1SessionTermination,
|
||||
EnableBackChannelLogout: tc.fields.EnableBackChannelLogout,
|
||||
LoginV2: tc.fields.LoginV2,
|
||||
PermissionCheckV2: tc.fields.PermissionCheckV2,
|
||||
ConsoleUseV2UserApi: tc.fields.ConsoleUseV2UserApi,
|
||||
EnableRelationalTables: tc.fields.EnableRelationalTables,
|
||||
}
|
||||
got, err := f.ToInstanceFeatures()
|
||||
|
||||
require.Equal(t, tc.wantErr, err != nil)
|
||||
assert.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ type SystemFeatures struct {
|
||||
EnableBackChannelLogout *bool
|
||||
LoginV2 *feature.LoginV2
|
||||
PermissionCheckV2 *bool
|
||||
EnableRelationalTables *bool
|
||||
}
|
||||
|
||||
func (m *SystemFeatures) isEmpty() bool {
|
||||
return m.LoginDefaultOrg == nil &&
|
||||
return m == nil || (m.LoginDefaultOrg == nil &&
|
||||
m.UserSchema == nil &&
|
||||
m.TokenExchange == nil &&
|
||||
// nil check to allow unset improvements
|
||||
@@ -29,7 +30,8 @@ func (m *SystemFeatures) isEmpty() bool {
|
||||
m.OIDCSingleV1SessionTermination == nil &&
|
||||
m.EnableBackChannelLogout == nil &&
|
||||
m.LoginV2 == nil &&
|
||||
m.PermissionCheckV2 == nil
|
||||
m.PermissionCheckV2 == nil &&
|
||||
m.EnableRelationalTables == nil)
|
||||
}
|
||||
|
||||
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
||||
|
||||
@@ -67,6 +67,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemEnableBackChannelLogout,
|
||||
feature_v2.SystemLoginVersion,
|
||||
feature_v2.SystemPermissionCheckV2,
|
||||
feature_v2.SystemEnableRelationalTables,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -101,6 +102,9 @@ func reduceSystemFeature(features *SystemFeatures, key feature.Key, value any) {
|
||||
case feature.KeyPermissionCheckV2:
|
||||
v := value.(bool)
|
||||
features.PermissionCheckV2 = &v
|
||||
case feature.KeyEnableRelationalTables:
|
||||
v := value.(bool)
|
||||
features.EnableRelationalTables = &v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,23 +119,24 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableBackChannelLogout, f.EnableBackChannelLogout, feature_v2.SystemEnableBackChannelLogout)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginV2, f.LoginV2, feature_v2.SystemLoginVersion)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.PermissionCheckV2, f.PermissionCheckV2, feature_v2.SystemPermissionCheckV2)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.EnableRelationalTables, f.EnableRelationalTables, feature_v2.SystemEnableRelationalTables)
|
||||
return cmds
|
||||
}
|
||||
|
||||
func appendFeatureUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValue, newValue *T, eventType eventstore.EventType) []eventstore.Command {
|
||||
if newValue != nil && (oldValue == nil || *oldValue != *newValue) {
|
||||
cmds = append(cmds, feature_v2.NewSetEvent[T](ctx, aggregate, eventType, *newValue))
|
||||
cmds = append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, *newValue))
|
||||
}
|
||||
return cmds
|
||||
}
|
||||
|
||||
func appendFeatureSliceUpdate[T comparable](ctx context.Context, cmds []eventstore.Command, aggregate *feature_v2.Aggregate, oldValues, newValues []T, eventType eventstore.EventType) []eventstore.Command {
|
||||
if len(newValues) != len(oldValues) {
|
||||
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
|
||||
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
|
||||
}
|
||||
for i, oldValue := range oldValues {
|
||||
if oldValue != newValues[i] {
|
||||
return append(cmds, feature_v2.NewSetEvent[[]T](ctx, aggregate, eventType, newValues))
|
||||
return append(cmds, feature_v2.NewSetEvent(ctx, aggregate, eventType, newValues))
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
|
||||
@@ -11,11 +11,14 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
"github.com/zitadel/zitadel/internal/repository/feature/feature_v2"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aggregate := feature_v2.NewAggregate("SYSTEM", "SYSTEM")
|
||||
|
||||
type args struct {
|
||||
@@ -50,7 +53,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
),
|
||||
@@ -68,7 +71,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
@@ -86,7 +89,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPushFailed(io.ErrClosedPipe,
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemEnableBackChannelLogout, true,
|
||||
),
|
||||
@@ -102,24 +105,29 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemEnableRelationalTables, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableRelationalTables: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
@@ -130,7 +138,7 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
eventstore: expectEventstore(
|
||||
// throw in some set events, reset and set again.
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -138,21 +146,21 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemResetEventType,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, false,
|
||||
)),
|
||||
),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemOIDCSingleV1SessionTerminationEventType, false,
|
||||
),
|
||||
@@ -170,6 +178,8 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := &Commands{
|
||||
eventstore: tt.eventstore(t),
|
||||
}
|
||||
@@ -199,7 +209,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
|
||||
name: "push error",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -214,7 +224,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
|
||||
name: "success",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -231,7 +241,7 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
|
||||
name: "no change after previous reset",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLoginDefaultOrgEventType, true,
|
||||
)),
|
||||
@@ -266,3 +276,32 @@ func TestCommands_ResetSystemFeatures(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemFeatures_isEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sysFeatures *SystemFeatures
|
||||
want bool
|
||||
}{
|
||||
{name: "nil features", sysFeatures: nil, want: true},
|
||||
{name: "empty features", sysFeatures: &SystemFeatures{}, want: true},
|
||||
{name: "LoginDefaultOrg set", sysFeatures: &SystemFeatures{LoginDefaultOrg: gu.Ptr(true)}, want: false},
|
||||
{name: "UserSchema set", sysFeatures: &SystemFeatures{UserSchema: gu.Ptr(true)}, want: false},
|
||||
{name: "TokenExchange set", sysFeatures: &SystemFeatures{TokenExchange: gu.Ptr(true)}, want: false},
|
||||
{name: "ImprovedPerformance set", sysFeatures: &SystemFeatures{ImprovedPerformance: []feature.ImprovedPerformanceType{}}, want: false},
|
||||
{name: "OIDCSingleV1SessionTermination set", sysFeatures: &SystemFeatures{OIDCSingleV1SessionTermination: gu.Ptr(true)}, want: false},
|
||||
{name: "EnableBackChannelLogout set", sysFeatures: &SystemFeatures{EnableBackChannelLogout: gu.Ptr(true)}, want: false},
|
||||
{name: "LoginV2 set", sysFeatures: &SystemFeatures{LoginV2: &feature.LoginV2{}}, want: false},
|
||||
{name: "PermissionCheckV2 set", sysFeatures: &SystemFeatures{PermissionCheckV2: gu.Ptr(true)}, want: false},
|
||||
{name: "EnableRelationalTables set", sysFeatures: &SystemFeatures{EnableRelationalTables: gu.Ptr(true)}, want: false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := tt.sysFeatures.isEmpty()
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
KeyLoginV2 Key = 13
|
||||
KeyPermissionCheckV2 Key = 14
|
||||
KeyConsoleUseV2UserApi Key = 15
|
||||
KeyEnableRelationalTables Key = 16
|
||||
)
|
||||
|
||||
//go:generate enumer -type Level -transform snake -trimprefix Level
|
||||
@@ -49,6 +50,7 @@ type Features struct {
|
||||
LoginV2 LoginV2 `json:"login_v2,omitempty"`
|
||||
PermissionCheckV2 bool `json:"permission_check_v2,omitempty"`
|
||||
ConsoleUseV2UserApi bool `json:"console_use_v2_user_api,omitempty"`
|
||||
EnableRelationalTables bool `json:"enable_relational_tables,omitempty"`
|
||||
}
|
||||
|
||||
/* Note: do not generate the stringer or enumer for this type, is it breaks existing events */
|
||||
|
||||
@@ -16,8 +16,8 @@ const (
|
||||
_KeyLowerName_2 = "improved_performance"
|
||||
_KeyName_3 = "debug_oidc_parent_erroroidc_single_v1_session_termination"
|
||||
_KeyLowerName_3 = "debug_oidc_parent_erroroidc_single_v1_session_termination"
|
||||
_KeyName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_api"
|
||||
_KeyLowerName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_api"
|
||||
_KeyName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_apienable_relational_tables"
|
||||
_KeyLowerName_4 = "enable_back_channel_logoutlogin_v2permission_check_v2console_use_v2_user_apienable_relational_tables"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -25,7 +25,7 @@ var (
|
||||
_KeyIndex_1 = [...]uint8{0, 11, 25}
|
||||
_KeyIndex_2 = [...]uint8{0, 20}
|
||||
_KeyIndex_3 = [...]uint8{0, 23, 57}
|
||||
_KeyIndex_4 = [...]uint8{0, 26, 34, 53, 76}
|
||||
_KeyIndex_4 = [...]uint8{0, 26, 34, 53, 76, 100}
|
||||
)
|
||||
|
||||
func (i Key) String() string {
|
||||
@@ -40,7 +40,7 @@ func (i Key) String() string {
|
||||
case 9 <= i && i <= 10:
|
||||
i -= 9
|
||||
return _KeyName_3[_KeyIndex_3[i]:_KeyIndex_3[i+1]]
|
||||
case 12 <= i && i <= 15:
|
||||
case 12 <= i && i <= 16:
|
||||
i -= 12
|
||||
return _KeyName_4[_KeyIndex_4[i]:_KeyIndex_4[i+1]]
|
||||
default:
|
||||
@@ -63,33 +63,36 @@ func _KeyNoOp() {
|
||||
_ = x[KeyLoginV2-(13)]
|
||||
_ = x[KeyPermissionCheckV2-(14)]
|
||||
_ = x[KeyConsoleUseV2UserApi-(15)]
|
||||
_ = x[KeyEnableRelationalTables-(16)]
|
||||
}
|
||||
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyUserSchema, KeyTokenExchange, KeyImprovedPerformance, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyEnableBackChannelLogout, KeyLoginV2, KeyPermissionCheckV2, KeyConsoleUseV2UserApi}
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyUserSchema, KeyTokenExchange, KeyImprovedPerformance, KeyDebugOIDCParentError, KeyOIDCSingleV1SessionTermination, KeyEnableBackChannelLogout, KeyLoginV2, KeyPermissionCheckV2, KeyConsoleUseV2UserApi, KeyEnableRelationalTables}
|
||||
|
||||
var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyName_0[0:11]: KeyUnspecified,
|
||||
_KeyLowerName_0[0:11]: KeyUnspecified,
|
||||
_KeyName_0[11:28]: KeyLoginDefaultOrg,
|
||||
_KeyLowerName_0[11:28]: KeyLoginDefaultOrg,
|
||||
_KeyName_1[0:11]: KeyUserSchema,
|
||||
_KeyLowerName_1[0:11]: KeyUserSchema,
|
||||
_KeyName_1[11:25]: KeyTokenExchange,
|
||||
_KeyLowerName_1[11:25]: KeyTokenExchange,
|
||||
_KeyName_2[0:20]: KeyImprovedPerformance,
|
||||
_KeyLowerName_2[0:20]: KeyImprovedPerformance,
|
||||
_KeyName_3[0:23]: KeyDebugOIDCParentError,
|
||||
_KeyLowerName_3[0:23]: KeyDebugOIDCParentError,
|
||||
_KeyName_3[23:57]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyLowerName_3[23:57]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyName_4[0:26]: KeyEnableBackChannelLogout,
|
||||
_KeyLowerName_4[0:26]: KeyEnableBackChannelLogout,
|
||||
_KeyName_4[26:34]: KeyLoginV2,
|
||||
_KeyLowerName_4[26:34]: KeyLoginV2,
|
||||
_KeyName_4[34:53]: KeyPermissionCheckV2,
|
||||
_KeyLowerName_4[34:53]: KeyPermissionCheckV2,
|
||||
_KeyName_4[53:76]: KeyConsoleUseV2UserApi,
|
||||
_KeyLowerName_4[53:76]: KeyConsoleUseV2UserApi,
|
||||
_KeyName_0[0:11]: KeyUnspecified,
|
||||
_KeyLowerName_0[0:11]: KeyUnspecified,
|
||||
_KeyName_0[11:28]: KeyLoginDefaultOrg,
|
||||
_KeyLowerName_0[11:28]: KeyLoginDefaultOrg,
|
||||
_KeyName_1[0:11]: KeyUserSchema,
|
||||
_KeyLowerName_1[0:11]: KeyUserSchema,
|
||||
_KeyName_1[11:25]: KeyTokenExchange,
|
||||
_KeyLowerName_1[11:25]: KeyTokenExchange,
|
||||
_KeyName_2[0:20]: KeyImprovedPerformance,
|
||||
_KeyLowerName_2[0:20]: KeyImprovedPerformance,
|
||||
_KeyName_3[0:23]: KeyDebugOIDCParentError,
|
||||
_KeyLowerName_3[0:23]: KeyDebugOIDCParentError,
|
||||
_KeyName_3[23:57]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyLowerName_3[23:57]: KeyOIDCSingleV1SessionTermination,
|
||||
_KeyName_4[0:26]: KeyEnableBackChannelLogout,
|
||||
_KeyLowerName_4[0:26]: KeyEnableBackChannelLogout,
|
||||
_KeyName_4[26:34]: KeyLoginV2,
|
||||
_KeyLowerName_4[26:34]: KeyLoginV2,
|
||||
_KeyName_4[34:53]: KeyPermissionCheckV2,
|
||||
_KeyLowerName_4[34:53]: KeyPermissionCheckV2,
|
||||
_KeyName_4[53:76]: KeyConsoleUseV2UserApi,
|
||||
_KeyLowerName_4[53:76]: KeyConsoleUseV2UserApi,
|
||||
_KeyName_4[76:100]: KeyEnableRelationalTables,
|
||||
_KeyLowerName_4[76:100]: KeyEnableRelationalTables,
|
||||
}
|
||||
|
||||
var _KeyNames = []string{
|
||||
@@ -104,6 +107,7 @@ var _KeyNames = []string{
|
||||
_KeyName_4[26:34],
|
||||
_KeyName_4[34:53],
|
||||
_KeyName_4[53:76],
|
||||
_KeyName_4[76:100],
|
||||
}
|
||||
|
||||
// KeyString retrieves an enum value from the enum constants string name.
|
||||
|
||||
@@ -19,6 +19,7 @@ type InstanceFeatures struct {
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
ConsoleUseV2UserApi FeatureSource[bool]
|
||||
EnableRelationalTables FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||
|
||||
@@ -72,11 +72,13 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceLoginVersion,
|
||||
feature_v2.InstancePermissionCheckV2,
|
||||
feature_v2.InstanceConsoleUseV2UserApi,
|
||||
feature_v2.InstanceEnableRelationalTables,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
|
||||
func (m *InstanceFeaturesReadModel) reduceReset() {
|
||||
m.instance.EnableRelationalTables = FeatureSource[bool]{}
|
||||
if m.populateFromSystem() {
|
||||
return
|
||||
}
|
||||
@@ -126,6 +128,8 @@ func reduceInstanceFeatureSet[T any](features *InstanceFeatures, event *feature_
|
||||
features.PermissionCheckV2.set(level, event.Value)
|
||||
case feature.KeyConsoleUseV2UserApi:
|
||||
features.ConsoleUseV2UserApi.set(level, event.Value)
|
||||
case feature.KeyEnableRelationalTables:
|
||||
features.EnableRelationalTables.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -104,6 +104,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceEnableRelationalTables,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type SystemFeatures struct {
|
||||
EnableBackChannelLogout FeatureSource[bool]
|
||||
LoginV2 FeatureSource[*feature.LoginV2]
|
||||
PermissionCheckV2 FeatureSource[bool]
|
||||
EnableRelationalTables FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||
|
||||
@@ -63,6 +63,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemEnableBackChannelLogout,
|
||||
feature_v2.SystemLoginVersion,
|
||||
feature_v2.SystemPermissionCheckV2,
|
||||
feature_v2.SystemEnableRelationalTables,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -96,6 +97,8 @@ func reduceSystemFeatureSet[T any](features *SystemFeatures, event *feature_v2.S
|
||||
features.LoginV2.set(level, event.Value)
|
||||
case feature.KeyPermissionCheckV2:
|
||||
features.PermissionCheckV2.set(level, event.Value)
|
||||
case feature.KeyEnableRelationalTables:
|
||||
features.EnableRelationalTables.set(level, event.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemEnableRelationalTables, false,
|
||||
)),
|
||||
),
|
||||
),
|
||||
want: &SystemFeatures{
|
||||
@@ -67,6 +71,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
EnableRelationalTables: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableBackChannelLogout, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemPermissionCheckV2, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemEnableRelationalTables, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
@@ -27,4 +28,5 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginVersion, eventstore.GenericEventMapper[SetEvent[*feature.LoginV2]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstancePermissionCheckV2, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceConsoleUseV2UserApi, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceEnableRelationalTables, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ var (
|
||||
SystemEnableBackChannelLogout = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableBackChannelLogout)
|
||||
SystemLoginVersion = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLoginV2)
|
||||
SystemPermissionCheckV2 = setEventTypeFromFeature(feature.LevelSystem, feature.KeyPermissionCheckV2)
|
||||
SystemEnableRelationalTables = setEventTypeFromFeature(feature.LevelSystem, feature.KeyEnableRelationalTables)
|
||||
|
||||
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
||||
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
||||
@@ -32,6 +33,7 @@ var (
|
||||
InstanceLoginVersion = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginV2)
|
||||
InstancePermissionCheckV2 = setEventTypeFromFeature(feature.LevelInstance, feature.KeyPermissionCheckV2)
|
||||
InstanceConsoleUseV2UserApi = setEventTypeFromFeature(feature.LevelInstance, feature.KeyConsoleUseV2UserApi)
|
||||
InstanceEnableRelationalTables = setEventTypeFromFeature(feature.LevelInstance, feature.KeyEnableRelationalTables)
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user