mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 05:07:31 +00:00
feat: implement user schema management (#7416)
This PR adds the functionality to manage user schemas through the new user schema service. It includes the possibility to create a basic JSON schema and also provides a way on defining permissions (read, write) for owner and self context with an annotation. Further annotations for OIDC claims and SAML attribute mappings will follow. A guide on how to create a schema and assign permissions has been started. It will be extended though out the process of implementing the schema and users based on those. Note: This feature is in an early stage and therefore not enabled by default. To test it out, please enable the UserSchema feature flag on your instance / system though the feature service.
This commit is contained in:
@@ -13,6 +13,7 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||
LegacyIntrospection: req.OidcLegacyIntrospection,
|
||||
UserSchema: req.UserSchema,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +23,7 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
LoginDefaultOrg: featureSourceToFlagPb(&f.LoginDefaultOrg),
|
||||
OidcTriggerIntrospectionProjections: featureSourceToFlagPb(&f.TriggerIntrospectionProjections),
|
||||
OidcLegacyIntrospection: featureSourceToFlagPb(&f.LegacyIntrospection),
|
||||
UserSchema: featureSourceToFlagPb(&f.UserSchema),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +32,7 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||
LegacyIntrospection: req.OidcLegacyIntrospection,
|
||||
UserSchema: req.UserSchema,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +42,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
LoginDefaultOrg: featureSourceToFlagPb(&f.LoginDefaultOrg),
|
||||
OidcTriggerIntrospectionProjections: featureSourceToFlagPb(&f.TriggerIntrospectionProjections),
|
||||
OidcLegacyIntrospection: featureSourceToFlagPb(&f.LegacyIntrospection),
|
||||
UserSchema: featureSourceToFlagPb(&f.UserSchema),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,11 +21,13 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
OidcTriggerIntrospectionProjections: gu.Ptr(false),
|
||||
OidcLegacyIntrospection: nil,
|
||||
UserSchema: gu.Ptr(true),
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: nil,
|
||||
UserSchema: gu.Ptr(true),
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -50,6 +52,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
UserSchema: query.FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -69,6 +75,10 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
UserSchema: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -79,11 +89,13 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
OidcTriggerIntrospectionProjections: gu.Ptr(false),
|
||||
OidcLegacyIntrospection: nil,
|
||||
UserSchema: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: nil,
|
||||
UserSchema: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -108,6 +120,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
UserSchema: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -127,6 +143,10 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
UserSchema: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@@ -218,6 +218,7 @@ func TestServer_GetSystemFeatures(t *testing.T) {
|
||||
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
|
||||
assertFeatureFlag(t, tt.want.OidcTriggerIntrospectionProjections, got.OidcTriggerIntrospectionProjections)
|
||||
assertFeatureFlag(t, tt.want.OidcLegacyIntrospection, got.OidcLegacyIntrospection)
|
||||
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -384,6 +385,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
UserSchema: &feature.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -392,6 +397,7 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
_, err := Client.SetInstanceFeatures(IamCTX, &feature.SetInstanceFeaturesRequest{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
OidcTriggerIntrospectionProjections: gu.Ptr(false),
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
@@ -408,6 +414,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
UserSchema: &feature.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -437,6 +447,10 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
UserSchema: &feature.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature.Source_SOURCE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -459,6 +473,7 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
assertFeatureFlag(t, tt.want.LoginDefaultOrg, got.LoginDefaultOrg)
|
||||
assertFeatureFlag(t, tt.want.OidcTriggerIntrospectionProjections, got.OidcTriggerIntrospectionProjections)
|
||||
assertFeatureFlag(t, tt.want.OidcLegacyIntrospection, got.OidcLegacyIntrospection)
|
||||
assertFeatureFlag(t, tt.want.UserSchema, got.UserSchema)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
150
internal/api/grpc/user/schema/v3alpha/schema.go
Normal file
150
internal/api/grpc/user/schema/v3alpha/schema.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) CreateUserSchema(ctx context.Context, req *schema.CreateUserSchemaRequest) (*schema.CreateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := createUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, details, err := s.command.CreateUserSchema(ctx, userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.CreateUserSchemaResponse{
|
||||
Id: id,
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateUserSchema(ctx context.Context, req *schema.UpdateUserSchemaRequest) (*schema.UpdateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := updateUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.UpdateUserSchema(ctx, userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.UpdateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
func (s *Server) DeactivateUserSchema(ctx context.Context, req *schema.DeactivateUserSchemaRequest) (*schema.DeactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeactivateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
func (s *Server) ReactivateUserSchema(ctx context.Context, req *schema.ReactivateUserSchemaRequest) (*schema.ReactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.ReactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.ReactivateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
func (s *Server) DeleteUserSchema(ctx context.Context, req *schema.DeleteUserSchemaRequest) (*schema.DeleteUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeleteUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkUserSchemaEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().UserSchema {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-SFjk3", "Errors.UserSchema.NotEnabled")
|
||||
}
|
||||
|
||||
func createUserSchemaToCommand(req *schema.CreateUserSchemaRequest, resourceOwner string) (*command.CreateUserSchema, error) {
|
||||
schema, err := req.GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.CreateUserSchema{
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.GetType(),
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateUserSchemaToCommand(req *schema.UpdateUserSchemaRequest, resourceOwner string) (*command.UpdateUserSchema, error) {
|
||||
schema, err := req.GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.UpdateUserSchema{
|
||||
ID: req.GetId(),
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.Type,
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authenticatorsToDomain(authenticators []schema.AuthenticatorType) []domain.AuthenticatorType {
|
||||
types := make([]domain.AuthenticatorType, len(authenticators))
|
||||
for i, authenticator := range authenticators {
|
||||
types[i] = authenticatorTypeToDomain(authenticator)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func authenticatorTypeToDomain(authenticator schema.AuthenticatorType) domain.AuthenticatorType {
|
||||
switch authenticator {
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME:
|
||||
return domain.AuthenticatorTypeUsername
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD:
|
||||
return domain.AuthenticatorTypePassword
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN:
|
||||
return domain.AuthenticatorTypeWebAuthN
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP:
|
||||
return domain.AuthenticatorTypeTOTP
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL:
|
||||
return domain.AuthenticatorTypeOTPEmail
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS:
|
||||
return domain.AuthenticatorTypeOTPSMS
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY:
|
||||
return domain.AuthenticatorTypeAuthenticationKey
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER:
|
||||
return domain.AuthenticatorTypeIdentityProvider
|
||||
default:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
}
|
||||
}
|
812
internal/api/grpc/user/schema/v3alpha/schema_integration_test.go
Normal file
812
internal/api/grpc/user/schema/v3alpha/schema_integration_test.go
Normal file
@@ -0,0 +1,812 @@
|
||||
//go:build integration
|
||||
|
||||
package schema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
Tester *integration.Tester
|
||||
Client schema.UserSchemaServiceClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, _, cancel := integration.Contexts(5 * time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Tester = integration.NewTester(ctx)
|
||||
defer Tester.Done()
|
||||
|
||||
CTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
|
||||
Client = Tester.Client.UserSchemaV3
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(CTX, &feature.SetInstanceFeaturesRequest{
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := CTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
100*time.Millisecond,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
||||
|
||||
func TestServer_CreateUserSchema(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *schema.CreateUserSchemaRequest
|
||||
want *schema.CreateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission, error",
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty schema, error",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no authenticators, ok",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "with authenticator, ok",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with invalid permission, error",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": "read"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "with valid permission, ok",
|
||||
ctx: CTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "rw",
|
||||
"self": "r"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
got, err := Client.CreateUserSchema(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
assert.NotEmpty(t, got.GetId())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.UpdateUserSchemaRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *schema.UpdateUserSchemaRequest) error
|
||||
args args
|
||||
want *schema.UpdateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing id, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
request.Id = "notexisting"
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
Type: gu.Ptr(""),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update type, ok",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
want: &schema.UpdateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty schema, ok",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
DataType: &schema.UpdateUserSchemaRequest_Schema{},
|
||||
},
|
||||
},
|
||||
want: &schema.UpdateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
DataType: &schema.UpdateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update schema, ok",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
DataType: &schema.UpdateUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.UpdateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update authenticator, ok",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.UpdateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inactive, error",
|
||||
prepare: func(request *schema.UpdateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.UpdateUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.UpdateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.DeactivateUserSchemaRequest
|
||||
prepare func(request *schema.DeactivateUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.DeactivateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&schema.DeactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "active, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.DeactivateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inactive, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.ReactivateUserSchemaRequest
|
||||
prepare func(request *schema.ReactivateUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.ReactivateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&schema.ReactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.ReactivateUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "active, error",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inactive, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &schema.ReactivateUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.ReactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.DeleteUserSchemaRequest
|
||||
prepare func(request *schema.DeleteUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.DeleteUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&schema.DeleteUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.DeleteUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.DeleteUserSchemaResponse{
|
||||
Details: &object.Details{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deleted, error",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeleteUserSchema(CTX, &schema.DeleteUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureFeatureEnabled(t)
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeleteUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertDetails(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
51
internal/api/grpc/user/schema/v3alpha/server.go
Normal file
51
internal/api/grpc/user/schema/v3alpha/server.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
)
|
||||
|
||||
var _ schema.UserSchemaServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
schema.UnimplementedUserSchemaServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
schema.RegisterUserSchemaServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return schema.UserSchemaService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return schema.UserSchemaService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return schema.UserSchemaService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return schema.RegisterUserSchemaServiceHandler
|
||||
}
|
@@ -15,12 +15,14 @@ type InstanceFeatures struct {
|
||||
LoginDefaultOrg *bool
|
||||
TriggerIntrospectionProjections *bool
|
||||
LegacyIntrospection *bool
|
||||
UserSchema *bool
|
||||
}
|
||||
|
||||
func (m *InstanceFeatures) isEmpty() bool {
|
||||
return m.LoginDefaultOrg == nil &&
|
||||
m.TriggerIntrospectionProjections == nil &&
|
||||
m.LegacyIntrospection == nil
|
||||
m.LegacyIntrospection == nil &&
|
||||
m.UserSchema == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetInstanceFeatures(ctx context.Context, f *InstanceFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@@ -54,6 +54,7 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceLoginDefaultOrgEventType,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
feature_v2.InstanceUserSchemaEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -62,6 +63,7 @@ func (m *InstanceFeaturesWriteModel) reduceReset() {
|
||||
m.LoginDefaultOrg = nil
|
||||
m.TriggerIntrospectionProjections = nil
|
||||
m.LegacyIntrospection = nil
|
||||
m.UserSchema = nil
|
||||
}
|
||||
|
||||
func (m *InstanceFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[bool]) error {
|
||||
@@ -78,6 +80,8 @@ func (m *InstanceFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEven
|
||||
m.TriggerIntrospectionProjections = &event.Value
|
||||
case feature.KeyLegacyIntrospection:
|
||||
m.LegacyIntrospection = &event.Value
|
||||
case feature.KeyUserSchema:
|
||||
m.UserSchema = &event.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -88,5 +92,6 @@ func (wm *InstanceFeaturesWriteModel) setCommands(ctx context.Context, f *Instan
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginDefaultOrg, f.LoginDefaultOrg, feature_v2.InstanceLoginDefaultOrgEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TriggerIntrospectionProjections, f.TriggerIntrospectionProjections, feature_v2.InstanceTriggerIntrospectionProjectionsEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LegacyIntrospection, f.LegacyIntrospection, feature_v2.InstanceLegacyIntrospectionEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.UserSchema, f.UserSchema, feature_v2.InstanceUserSchemaEventType)
|
||||
return cmds
|
||||
}
|
||||
|
@@ -131,6 +131,24 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ResourceOwner: "instance1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set UserSchema",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{ctx, &InstanceFeatures{
|
||||
UserSchema: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "push error",
|
||||
eventstore: expectEventstore(
|
||||
@@ -164,12 +182,17 @@ func TestCommands_SetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{ctx, &InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "instance1",
|
||||
|
@@ -12,12 +12,14 @@ type SystemFeatures struct {
|
||||
LoginDefaultOrg *bool
|
||||
TriggerIntrospectionProjections *bool
|
||||
LegacyIntrospection *bool
|
||||
UserSchema *bool
|
||||
}
|
||||
|
||||
func (m *SystemFeatures) isEmpty() bool {
|
||||
return m.LoginDefaultOrg == nil &&
|
||||
m.TriggerIntrospectionProjections == nil &&
|
||||
m.LegacyIntrospection == nil
|
||||
m.LegacyIntrospection == nil &&
|
||||
m.UserSchema == nil
|
||||
}
|
||||
|
||||
func (c *Commands) SetSystemFeatures(ctx context.Context, f *SystemFeatures) (*domain.ObjectDetails, error) {
|
||||
|
@@ -49,6 +49,7 @@ func (m *SystemFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemLoginDefaultOrgEventType,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.SystemLegacyIntrospectionEventType,
|
||||
feature_v2.SystemUserSchemaEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -57,6 +58,7 @@ func (m *SystemFeaturesWriteModel) reduceReset() {
|
||||
m.LoginDefaultOrg = nil
|
||||
m.TriggerIntrospectionProjections = nil
|
||||
m.LegacyIntrospection = nil
|
||||
m.UserSchema = nil
|
||||
}
|
||||
|
||||
func (m *SystemFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[bool]) error {
|
||||
@@ -73,6 +75,8 @@ func (m *SystemFeaturesWriteModel) reduceBoolFeature(event *feature_v2.SetEvent[
|
||||
m.TriggerIntrospectionProjections = &event.Value
|
||||
case feature.KeyLegacyIntrospection:
|
||||
m.LegacyIntrospection = &event.Value
|
||||
case feature.KeyUserSchema:
|
||||
m.UserSchema = &event.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -83,6 +87,7 @@ func (wm *SystemFeaturesWriteModel) setCommands(ctx context.Context, f *SystemFe
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LoginDefaultOrg, f.LoginDefaultOrg, feature_v2.SystemLoginDefaultOrgEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.TriggerIntrospectionProjections, f.TriggerIntrospectionProjections, feature_v2.SystemTriggerIntrospectionProjectionsEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.LegacyIntrospection, f.LegacyIntrospection, feature_v2.SystemLegacyIntrospectionEventType)
|
||||
cmds = appendFeatureUpdate(ctx, cmds, aggregate, wm.UserSchema, f.UserSchema, feature_v2.SystemUserSchemaEventType)
|
||||
return cmds
|
||||
}
|
||||
|
||||
|
@@ -99,6 +99,24 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
ResourceOwner: "SYSTEM",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set UserSchema",
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
UserSchema: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "push error",
|
||||
eventstore: expectEventstore(
|
||||
@@ -132,12 +150,17 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, true,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
@@ -178,12 +201,17 @@ func TestCommands_SetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType, false,
|
||||
),
|
||||
feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, true,
|
||||
),
|
||||
),
|
||||
),
|
||||
args: args{context.Background(), &SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
TriggerIntrospectionProjections: gu.Ptr(false),
|
||||
LegacyIntrospection: gu.Ptr(true),
|
||||
UserSchema: gu.Ptr(true),
|
||||
}},
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "SYSTEM",
|
||||
|
180
internal/command/user_schema.go
Normal file
180
internal/command/user_schema.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schema"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type CreateUserSchema struct {
|
||||
ResourceOwner string
|
||||
Type string
|
||||
Schema json.RawMessage
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
}
|
||||
|
||||
func (s *CreateUserSchema) Valid() error {
|
||||
if s.Type == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-DGFj3", "Errors.UserSchema.Type.Missing")
|
||||
}
|
||||
if err := validateUserSchema(s.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, authenticator := range s.PossibleAuthenticators {
|
||||
if authenticator == domain.AuthenticatorTypeUnspecified {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-Gh652", "Errors.UserSchema.Authenticator.Invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateUserSchema struct {
|
||||
ID string
|
||||
ResourceOwner string
|
||||
Type *string
|
||||
Schema json.RawMessage
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
}
|
||||
|
||||
func (s *UpdateUserSchema) Valid() error {
|
||||
if s.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing")
|
||||
}
|
||||
if s.Type != nil && *s.Type == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-G43gn", "Errors.UserSchema.Type.Missing")
|
||||
}
|
||||
if err := validateUserSchema(s.Schema); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, authenticator := range s.PossibleAuthenticators {
|
||||
if authenticator == domain.AuthenticatorTypeUnspecified {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-WF4hg", "Errors.UserSchema.Authenticator.Invalid")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateUserSchema(ctx context.Context, userSchema *CreateUserSchema) (string, *domain.ObjectDetails, error) {
|
||||
if err := userSchema.Valid(); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if userSchema.ResourceOwner == "" {
|
||||
return "", nil, zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, userSchema.ResourceOwner)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewCreatedEvent(ctx,
|
||||
UserSchemaAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
userSchema.Type, userSchema.Schema, userSchema.PossibleAuthenticators,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateUserSchema(ctx context.Context, userSchema *UpdateUserSchema) (*domain.ObjectDetails, error) {
|
||||
if err := userSchema.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(userSchema.ID, userSchema.ResourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive")
|
||||
}
|
||||
updatedEvent := writeModel.NewUpdatedEvent(
|
||||
ctx,
|
||||
UserSchemaAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
userSchema.Type,
|
||||
userSchema.Schema,
|
||||
userSchema.PossibleAuthenticators,
|
||||
)
|
||||
if updatedEvent == nil {
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, updatedEvent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserSchema(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-Vvf3w", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-E4t4z", "Errors.UserSchema.NotActive")
|
||||
}
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewDeactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ReactivateUserSchema(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-wq3Gw", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-DGzh5", "Errors.UserSchema.NotInactive")
|
||||
}
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewReactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteUserSchema(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-E22gg", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-Grg41", "Errors.UserSchema.NotExists")
|
||||
}
|
||||
// TODO: check for users based on that schema; this is only possible with / after https://github.com/zitadel/zitadel/issues/7308
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewDeletedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel), writeModel.SchemaType),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
}
|
||||
|
||||
func validateUserSchema(userSchema json.RawMessage) error {
|
||||
_, err := domain_schema.NewSchema(0, bytes.NewReader(userSchema))
|
||||
if err != nil {
|
||||
return zerrors.ThrowInvalidArgument(err, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid")
|
||||
}
|
||||
return nil
|
||||
}
|
112
internal/command/user_schema_model.go
Normal file
112
internal/command/user_schema_model.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schema"
|
||||
)
|
||||
|
||||
type UserSchemaWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
SchemaType string
|
||||
Schema json.RawMessage
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
State domain.UserSchemaState
|
||||
}
|
||||
|
||||
func NewUserSchemaWriteModel(schemaID, resourceOwner string) *UserSchemaWriteModel {
|
||||
return &UserSchemaWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: schemaID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserSchemaWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *schema.CreatedEvent:
|
||||
wm.SchemaType = e.SchemaType
|
||||
wm.Schema = e.Schema
|
||||
wm.PossibleAuthenticators = e.PossibleAuthenticators
|
||||
wm.State = domain.UserSchemaStateActive
|
||||
case *schema.UpdatedEvent:
|
||||
if e.SchemaType != nil {
|
||||
wm.SchemaType = *e.SchemaType
|
||||
}
|
||||
if len(e.Schema) > 0 {
|
||||
wm.Schema = e.Schema
|
||||
}
|
||||
if len(e.PossibleAuthenticators) > 0 {
|
||||
wm.PossibleAuthenticators = e.PossibleAuthenticators
|
||||
}
|
||||
case *schema.DeactivatedEvent:
|
||||
wm.State = domain.UserSchemaStateInactive
|
||||
case *schema.ReactivatedEvent:
|
||||
wm.State = domain.UserSchemaStateActive
|
||||
case *schema.DeletedEvent:
|
||||
wm.State = domain.UserSchemaStateDeleted
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(schema.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
schema.CreatedType,
|
||||
schema.UpdatedType,
|
||||
schema.DeactivatedType,
|
||||
schema.ReactivatedType,
|
||||
schema.DeletedType,
|
||||
).
|
||||
Builder()
|
||||
}
|
||||
func (wm *UserSchemaWriteModel) NewUpdatedEvent(
|
||||
ctx context.Context,
|
||||
agg *eventstore.Aggregate,
|
||||
schemaType *string,
|
||||
userSchema json.RawMessage,
|
||||
possibleAuthenticators []domain.AuthenticatorType,
|
||||
) *schema.UpdatedEvent {
|
||||
changes := make([]schema.Changes, 0)
|
||||
if schemaType != nil && wm.SchemaType != *schemaType {
|
||||
changes = append(changes, schema.ChangeSchemaType(wm.SchemaType, *schemaType))
|
||||
}
|
||||
if !bytes.Equal(wm.Schema, userSchema) {
|
||||
changes = append(changes, schema.ChangeSchema(userSchema))
|
||||
}
|
||||
if len(possibleAuthenticators) > 0 && slices.Compare(wm.PossibleAuthenticators, possibleAuthenticators) != 0 {
|
||||
changes = append(changes, schema.ChangePossibleAuthenticators(possibleAuthenticators))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return schema.NewUpdatedEvent(ctx, agg, changes)
|
||||
}
|
||||
|
||||
func UserSchemaAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: wm.AggregateID,
|
||||
Type: schema.AggregateType,
|
||||
ResourceOwner: wm.ResourceOwner,
|
||||
InstanceID: wm.InstanceID,
|
||||
Version: schema.AggregateVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserSchemaWriteModel) Exists() bool {
|
||||
return wm.State != domain.UserSchemaStateUnspecified && wm.State != domain.UserSchemaStateDeleted
|
||||
}
|
912
internal/command/user_schema_test.go
Normal file
912
internal/command/user_schema_test.go
Normal file
@@ -0,0 +1,912 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
"github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schema"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userSchema *CreateUserSchema
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no type, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-DGFj3", "Errors.UserSchema.Type.Missing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"no schema, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
Type: "type",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid authenticator, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-Gh652", "Errors.UserSchema.Authenticator.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"no resourceOwner, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"empty user schema created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
ResourceOwner: "instanceID",
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"user schema created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
ResourceOwner: "instanceID",
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"user schema with invalid permission, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
ResourceOwner: "instanceID",
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": true
|
||||
}
|
||||
}
|
||||
}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"user schema with permission created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: mock.ExpectID(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &CreateUserSchema{
|
||||
ResourceOwner: "instanceID",
|
||||
Type: "type",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
gotID, gotDetails, err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
assert.Equal(t, tt.res.id, gotID)
|
||||
assert.Equal(t, tt.res.details, gotDetails)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userSchema *UpdateUserSchema
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"missing id, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"empty type, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr(""),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-G43gn", "Errors.UserSchema.Type.Missing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"no schema, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid schema, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
}
|
||||
}
|
||||
}`),
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid authenticator, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUnspecified,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-WF4hg", "Errors.UserSchema.Authenticator.Invalid"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"not active / exists, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr("type"),
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"no changes",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr("type"),
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"update type",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewUpdatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
[]schema.Changes{schema.ChangeSchemaType("type", "newType")},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
Type: gu.Ptr("newType"),
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"update schema",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewUpdatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
[]schema.Changes{schema.ChangeSchema(json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`))},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
Type: nil,
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"update possible authenticators",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewUpdatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
[]schema.Changes{schema.ChangePossibleAuthenticators([]domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
domain.AuthenticatorTypePassword,
|
||||
})},
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
domain.AuthenticatorTypeUsername,
|
||||
domain.AuthenticatorTypePassword,
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.UpdateUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.details, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_DeactivateUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"missing id, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-Vvf3w", "Errors.IDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"not active / exists, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMA-E4t4z", "Errors.UserSchema.NotActive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"deactivate ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewDeactivatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.DeactivateUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.details, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_ReactivateUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"missing id, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-wq3Gw", "Errors.IDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"not deactivated / exists, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMA-DGzh5", "Errors.UserSchema.NotInactive"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"reactivate ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
schema.NewDeactivatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewReactivatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.ReactivateUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.details, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_DeleteUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"missing id, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-E22gg", "Errors.IDMissing"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"not exists, error",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowPreconditionFailed(nil, "COMMA-Grg41", "Errors.UserSchema.NotExists"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"delete ok",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
json.RawMessage(`{}`),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername},
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
schema.NewDeletedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
"type",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
id: "id1",
|
||||
},
|
||||
res{
|
||||
details: &domain.ObjectDetails{
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.DeleteUserSchema(tt.args.ctx, tt.args.id, tt.args.resourceOwner)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assert.Equal(t, tt.res.details, got)
|
||||
})
|
||||
}
|
||||
}
|
120
internal/domain/schema/permission.go
Normal file
120
internal/domain/schema/permission.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed permission.schema.v1.json
|
||||
permissionJSON string
|
||||
|
||||
permissionSchema = jsonschema.MustCompileString(PermissionSchemaID, permissionJSON)
|
||||
)
|
||||
|
||||
const (
|
||||
PermissionSchemaID = "urn:zitadel:schema:permission-schema:v1"
|
||||
PermissionProperty = "urn:zitadel:schema:permission"
|
||||
)
|
||||
|
||||
type role int32
|
||||
|
||||
const (
|
||||
roleUnspecified role = iota
|
||||
roleSelf
|
||||
roleOwner
|
||||
)
|
||||
|
||||
type permissionExtension struct {
|
||||
role role
|
||||
}
|
||||
|
||||
// Compile implements the [jsonschema.ExtCompiler] interface.
|
||||
// It parses the permission schema extension / annotation of the passed field.
|
||||
func (c permissionExtension) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (_ jsonschema.ExtSchema, err error) {
|
||||
perm, ok := m[PermissionProperty]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
p, ok := perm.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-WR5gs", "invalid permission")
|
||||
}
|
||||
perms := new(permissions)
|
||||
for key, value := range p {
|
||||
switch key {
|
||||
case "self":
|
||||
perms.self, err = mapPermission(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
case "owner":
|
||||
perms.owner, err = mapPermission(value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission role")
|
||||
}
|
||||
}
|
||||
return permissionExtensionConfig{c.role, perms}, nil
|
||||
}
|
||||
|
||||
type permissionExtensionConfig struct {
|
||||
role role
|
||||
permissions *permissions
|
||||
}
|
||||
|
||||
// Validate implements the [jsonschema.ExtSchema] interface.
|
||||
// It validates the fields of the json instance according to the permission schema.
|
||||
func (s permissionExtensionConfig) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
|
||||
switch s.role {
|
||||
case roleSelf:
|
||||
if s.permissions.self == nil || !s.permissions.self.write {
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
}
|
||||
return nil
|
||||
case roleOwner:
|
||||
if s.permissions.owner == nil || !s.permissions.owner.write {
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
}
|
||||
return nil
|
||||
case roleUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
}
|
||||
}
|
||||
|
||||
func mapPermission(value any) (*permission, error) {
|
||||
p := new(permission)
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
for _, s := range v {
|
||||
switch s {
|
||||
case 'r':
|
||||
p.read = true
|
||||
case 'w':
|
||||
p.write = true
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-EZ5zjh", "invalid permission pattern: `%s` in (%s)", string(s), v)
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-E5h31", "invalid permission type %T (%v)", v, v)
|
||||
}
|
||||
}
|
||||
|
||||
type permissions struct {
|
||||
self *permission
|
||||
owner *permission
|
||||
}
|
||||
|
||||
type permission struct {
|
||||
read bool
|
||||
write bool
|
||||
}
|
28
internal/domain/schema/permission.schema.v1.json
Normal file
28
internal/domain/schema/permission.schema.v1.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$id": "urn:zitadel:schema:permission-schema:v1",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"urn:zitadel:schema:property-permission": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[rw]$"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"urn:zitadel:schema:permission": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"owner": {
|
||||
"$ref": "#/$defs/urn:zitadel:schema:property-permission"
|
||||
},
|
||||
"self": {
|
||||
"$ref": "#/$defs/urn:zitadel:schema:property-permission"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
253
internal/domain/schema/permission_test.go
Normal file
253
internal/domain/schema/permission_test.go
Normal file
@@ -0,0 +1,253 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestPermissionExtension(t *testing.T) {
|
||||
type args struct {
|
||||
role role
|
||||
schema string
|
||||
instance string
|
||||
}
|
||||
type want struct {
|
||||
compilationErr error
|
||||
validationErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
"invalid permission, compilation err",
|
||||
args{
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": "read"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
want{
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-WR5gs", "invalid permission"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid permission string, compilation err",
|
||||
args{
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"self": "read"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
want{
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-EZ5zjh", "invalid permission pattern: `e` in (read)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid permission type, compilation err",
|
||||
args{
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
want{
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-E5h31", "invalid permission type bool (true)"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid role, compilation err",
|
||||
args{
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"IAM_OWNER": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
},
|
||||
want{
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission role"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid permission self, validation err",
|
||||
args{
|
||||
role: roleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "rw",
|
||||
"self": "r"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid permission owner, validation err",
|
||||
args{
|
||||
role: roleOwner,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "r",
|
||||
"self": "r"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"valid permission self, ok",
|
||||
args{
|
||||
role: roleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "r",
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"valid permission owner, ok",
|
||||
args{
|
||||
role: roleOwner,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "rw",
|
||||
"self": "r"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no role, validation err",
|
||||
args{
|
||||
role: roleUnspecified,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "rw",
|
||||
"self": "rw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
"no permission required, ok",
|
||||
args{
|
||||
role: roleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`,
|
||||
instance: `{ "name": "test"}`,
|
||||
},
|
||||
want{
|
||||
validationErr: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
schema, err := NewSchema(tt.args.role, strings.NewReader(tt.args.schema))
|
||||
require.ErrorIs(t, err, tt.want.compilationErr)
|
||||
if tt.want.compilationErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
err = json.Unmarshal([]byte(tt.args.instance), &v)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = schema.Validate(v)
|
||||
if tt.want.validationErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
41
internal/domain/schema/schema.go
Normal file
41
internal/domain/schema/schema.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed zitadel.schema.v1.json
|
||||
zitadelJSON string
|
||||
)
|
||||
|
||||
const (
|
||||
MetaSchemaID = "urn:zitadel:schema:v1"
|
||||
)
|
||||
|
||||
func NewSchema(role role, r io.Reader) (*jsonschema.Schema, error) {
|
||||
c := jsonschema.NewCompiler()
|
||||
if err := c.AddResource(PermissionSchemaID, strings.NewReader(permissionJSON)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.AddResource(MetaSchemaID, strings.NewReader(zitadelJSON)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.RegisterExtension(PermissionSchemaID, permissionSchema, permissionExtension{
|
||||
role,
|
||||
})
|
||||
if err := c.AddResource("schema.json", r); err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-Frh42", "Errors.UserSchema.Schema.Invalid")
|
||||
}
|
||||
schema, err := c.Compile("schema.json")
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid")
|
||||
}
|
||||
return schema, nil
|
||||
}
|
13
internal/domain/schema/zitadel.schema.v1.json
Normal file
13
internal/domain/schema/zitadel.schema.v1.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$id": "urn:zitadel:schema:v1",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "https://json-schema.org/draft/2020-12/schema"
|
||||
},
|
||||
{
|
||||
"$ref": "urn:zitadel:schema:permission-schema:v1"
|
||||
}
|
||||
]
|
||||
}
|
26
internal/domain/user_schema.go
Normal file
26
internal/domain/user_schema.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package domain
|
||||
|
||||
type UserSchemaState int32
|
||||
|
||||
const (
|
||||
UserSchemaStateUnspecified UserSchemaState = iota
|
||||
UserSchemaStateActive
|
||||
UserSchemaStateInactive
|
||||
UserSchemaStateDeleted
|
||||
userSchemaStateCount
|
||||
)
|
||||
|
||||
type AuthenticatorType int32
|
||||
|
||||
const (
|
||||
AuthenticatorTypeUnspecified AuthenticatorType = iota
|
||||
AuthenticatorTypeUsername
|
||||
AuthenticatorTypePassword
|
||||
AuthenticatorTypeWebAuthN
|
||||
AuthenticatorTypeTOTP
|
||||
AuthenticatorTypeOTPEmail
|
||||
AuthenticatorTypeOTPSMS
|
||||
AuthenticatorTypeAuthenticationKey
|
||||
AuthenticatorTypeIdentityProvider
|
||||
authenticatorTypeCount
|
||||
)
|
@@ -8,6 +8,7 @@ const (
|
||||
KeyLoginDefaultOrg
|
||||
KeyTriggerIntrospectionProjections
|
||||
KeyLegacyIntrospection
|
||||
KeyUserSchema
|
||||
)
|
||||
|
||||
//go:generate enumer -type Level -transform snake -trimprefix Level
|
||||
@@ -27,4 +28,5 @@ type Features struct {
|
||||
LoginDefaultOrg bool `json:"login_default_org,omitempty"`
|
||||
TriggerIntrospectionProjections bool `json:"trigger_introspection_projections,omitempty"`
|
||||
LegacyIntrospection bool `json:"legacy_introspection,omitempty"`
|
||||
UserSchema bool `json:"user_schema,omitempty"`
|
||||
}
|
||||
|
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspection"
|
||||
const _KeyName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schema"
|
||||
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81}
|
||||
var _KeyIndex = [...]uint8{0, 11, 28, 61, 81, 92}
|
||||
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspection"
|
||||
const _KeyLowerName = "unspecifiedlogin_default_orgtrigger_introspection_projectionslegacy_introspectionuser_schema"
|
||||
|
||||
func (i Key) String() string {
|
||||
if i < 0 || i >= Key(len(_KeyIndex)-1) {
|
||||
@@ -28,9 +28,10 @@ func _KeyNoOp() {
|
||||
_ = x[KeyLoginDefaultOrg-(1)]
|
||||
_ = x[KeyTriggerIntrospectionProjections-(2)]
|
||||
_ = x[KeyLegacyIntrospection-(3)]
|
||||
_ = x[KeyUserSchema-(4)]
|
||||
}
|
||||
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection}
|
||||
var _KeyValues = []Key{KeyUnspecified, KeyLoginDefaultOrg, KeyTriggerIntrospectionProjections, KeyLegacyIntrospection, KeyUserSchema}
|
||||
|
||||
var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyName[0:11]: KeyUnspecified,
|
||||
@@ -41,6 +42,8 @@ var _KeyNameToValueMap = map[string]Key{
|
||||
_KeyLowerName[28:61]: KeyTriggerIntrospectionProjections,
|
||||
_KeyName[61:81]: KeyLegacyIntrospection,
|
||||
_KeyLowerName[61:81]: KeyLegacyIntrospection,
|
||||
_KeyName[81:92]: KeyUserSchema,
|
||||
_KeyLowerName[81:92]: KeyUserSchema,
|
||||
}
|
||||
|
||||
var _KeyNames = []string{
|
||||
@@ -48,6 +51,7 @@ var _KeyNames = []string{
|
||||
_KeyName[11:28],
|
||||
_KeyName[28:61],
|
||||
_KeyName[61:81],
|
||||
_KeyName[81:92],
|
||||
}
|
||||
|
||||
// KeyString retrieves an enum value from the enum constants string name.
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
@@ -37,38 +38,41 @@ import (
|
||||
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2 user.UserServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
SettingsV2 settings.SettingsServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2 organisation.OrganizationServiceClient
|
||||
System system.SystemServiceClient
|
||||
ExecutionV3 execution.ExecutionServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
CC *grpc.ClientConn
|
||||
Admin admin.AdminServiceClient
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2 user.UserServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
SettingsV2 settings.SettingsServiceClient
|
||||
OIDCv2 oidc_pb.OIDCServiceClient
|
||||
OrgV2 organisation.OrganizationServiceClient
|
||||
System system.SystemServiceClient
|
||||
ExecutionV3 execution.ExecutionServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
UserSchemaV3 schema.UserSchemaServiceClient
|
||||
}
|
||||
|
||||
func newClient(cc *grpc.ClientConn) Client {
|
||||
return Client{
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2: user.NewUserServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
SettingsV2: settings.NewSettingsServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
||||
System: system.NewSystemServiceClient(cc),
|
||||
ExecutionV3: execution.NewExecutionServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
CC: cc,
|
||||
Admin: admin.NewAdminServiceClient(cc),
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2: user.NewUserServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
SettingsV2: settings.NewSettingsServiceClient(cc),
|
||||
OIDCv2: oidc_pb.NewOIDCServiceClient(cc),
|
||||
OrgV2: organisation.NewOrganizationServiceClient(cc),
|
||||
System: system.NewSystemServiceClient(cc),
|
||||
ExecutionV3: execution.NewExecutionServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
UserSchemaV3: schema.NewUserSchemaServiceClient(cc),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,3 +544,21 @@ func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
target, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &schema.CreateUserSchemaRequest{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: userSchema,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ type InstanceFeatures struct {
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
TriggerIntrospectionProjections FeatureSource[bool]
|
||||
LegacyIntrospection FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetInstanceFeatures(ctx context.Context, cascade bool) (_ *InstanceFeatures, err error) {
|
||||
|
@@ -60,6 +60,7 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.InstanceLoginDefaultOrgEventType,
|
||||
feature_v2.InstanceTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
feature_v2.InstanceUserSchemaEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -71,6 +72,7 @@ func (m *InstanceFeaturesReadModel) reduceReset() {
|
||||
m.instance.LoginDefaultOrg = FeatureSource[bool]{}
|
||||
m.instance.TriggerIntrospectionProjections = FeatureSource[bool]{}
|
||||
m.instance.LegacyIntrospection = FeatureSource[bool]{}
|
||||
m.instance.UserSchema = FeatureSource[bool]{}
|
||||
}
|
||||
|
||||
func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
||||
@@ -80,6 +82,7 @@ func (m *InstanceFeaturesReadModel) populateFromSystem() bool {
|
||||
m.instance.LoginDefaultOrg = m.system.LoginDefaultOrg
|
||||
m.instance.TriggerIntrospectionProjections = m.system.TriggerIntrospectionProjections
|
||||
m.instance.LegacyIntrospection = m.system.LegacyIntrospection
|
||||
m.instance.UserSchema = m.system.UserSchema
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -99,6 +102,8 @@ func (m *InstanceFeaturesReadModel) reduceBoolFeature(event *feature_v2.SetEvent
|
||||
dst = &m.instance.TriggerIntrospectionProjections
|
||||
case feature.KeyLegacyIntrospection:
|
||||
dst = &m.instance.LegacyIntrospection
|
||||
case feature.KeyUserSchema:
|
||||
dst = &m.instance.UserSchema
|
||||
}
|
||||
*dst = FeatureSource[bool]{
|
||||
Level: level,
|
||||
|
@@ -101,6 +101,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
)),
|
||||
),
|
||||
),
|
||||
args: args{true},
|
||||
@@ -120,6 +124,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -142,6 +150,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewResetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceResetEventType,
|
||||
@@ -169,6 +181,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -187,6 +203,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceUserSchemaEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewResetEvent(
|
||||
ctx, aggregate,
|
||||
feature_v2.InstanceResetEventType,
|
||||
@@ -214,6 +234,10 @@ func TestQueries_GetInstanceFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -71,6 +71,10 @@ func (*instanceFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.InstanceLegacyIntrospectionEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.InstanceUserSchemaEventType,
|
||||
Reduce: reduceInstanceSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(InstanceDomainInstanceIDCol),
|
||||
|
@@ -63,6 +63,10 @@ func (*systemFeatureProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: feature_v2.SystemLegacyIntrospectionEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
},
|
||||
{
|
||||
Event: feature_v2.SystemUserSchemaEventType,
|
||||
Reduce: reduceSystemSetFeature[bool],
|
||||
},
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ type SystemFeatures struct {
|
||||
LoginDefaultOrg FeatureSource[bool]
|
||||
TriggerIntrospectionProjections FeatureSource[bool]
|
||||
LegacyIntrospection FeatureSource[bool]
|
||||
UserSchema FeatureSource[bool]
|
||||
}
|
||||
|
||||
func (q *Queries) GetSystemFeatures(ctx context.Context) (_ *SystemFeatures, err error) {
|
||||
|
@@ -48,6 +48,7 @@ func (m *SystemFeaturesReadModel) Query() *eventstore.SearchQueryBuilder {
|
||||
feature_v2.SystemLoginDefaultOrgEventType,
|
||||
feature_v2.SystemTriggerIntrospectionProjectionsEventType,
|
||||
feature_v2.SystemLegacyIntrospectionEventType,
|
||||
feature_v2.SystemUserSchemaEventType,
|
||||
).
|
||||
Builder().ResourceOwner(m.ResourceOwner)
|
||||
}
|
||||
@@ -72,6 +73,8 @@ func (m *SystemFeaturesReadModel) reduceBoolFeature(event *feature_v2.SetEvent[b
|
||||
dst = &m.system.TriggerIntrospectionProjections
|
||||
case feature.KeyLegacyIntrospection:
|
||||
dst = &m.system.LegacyIntrospection
|
||||
case feature.KeyUserSchema:
|
||||
dst = &m.system.UserSchema
|
||||
}
|
||||
|
||||
*dst = FeatureSource[bool]{
|
||||
|
@@ -57,6 +57,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
)),
|
||||
),
|
||||
),
|
||||
want: &SystemFeatures{
|
||||
@@ -75,6 +79,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -93,6 +101,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewResetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemResetEventType,
|
||||
@@ -119,6 +131,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -137,6 +153,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemLegacyIntrospectionEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewSetEvent[bool](
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemUserSchemaEventType, false,
|
||||
)),
|
||||
eventFromEventPusher(feature_v2.NewResetEvent(
|
||||
context.Background(), aggregate,
|
||||
feature_v2.SystemResetEventType,
|
||||
@@ -163,6 +183,10 @@ func TestQueries_GetSystemFeatures(t *testing.T) {
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
UserSchema: FeatureSource[bool]{
|
||||
Level: feature.LevelUnspecified,
|
||||
Value: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@@ -9,8 +9,10 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemTriggerIntrospectionProjectionsEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemLegacyIntrospectionEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SystemUserSchemaEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceResetEventType, eventstore.GenericEventMapper[ResetEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLoginDefaultOrgEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceTriggerIntrospectionProjectionsEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceLegacyIntrospectionEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, InstanceUserSchemaEventType, eventstore.GenericEventMapper[SetEvent[bool]])
|
||||
}
|
||||
|
@@ -15,11 +15,13 @@ var (
|
||||
SystemLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLoginDefaultOrg)
|
||||
SystemTriggerIntrospectionProjectionsEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyTriggerIntrospectionProjections)
|
||||
SystemLegacyIntrospectionEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyLegacyIntrospection)
|
||||
SystemUserSchemaEventType = setEventTypeFromFeature(feature.LevelSystem, feature.KeyUserSchema)
|
||||
|
||||
InstanceResetEventType = resetEventTypeFromFeature(feature.LevelInstance)
|
||||
InstanceLoginDefaultOrgEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLoginDefaultOrg)
|
||||
InstanceTriggerIntrospectionProjectionsEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyTriggerIntrospectionProjections)
|
||||
InstanceLegacyIntrospectionEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyLegacyIntrospection)
|
||||
InstanceUserSchemaEventType = setEventTypeFromFeature(feature.LevelInstance, feature.KeyUserSchema)
|
||||
)
|
||||
|
||||
const (
|
||||
|
25
internal/repository/user/schema/aggregate.go
Normal file
25
internal/repository/user/schema/aggregate.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
AggregateType = "user_schema"
|
||||
AggregateVersion = "v1"
|
||||
)
|
||||
|
||||
type Aggregate struct {
|
||||
eventstore.Aggregate
|
||||
}
|
||||
|
||||
func NewAggregate(id, resourceOwner string) *Aggregate {
|
||||
return &Aggregate{
|
||||
Aggregate: eventstore.Aggregate{
|
||||
Type: AggregateType,
|
||||
Version: AggregateVersion,
|
||||
ID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
11
internal/repository/user/schema/eventstore.go
Normal file
11
internal/repository/user/schema/eventstore.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package schema
|
||||
|
||||
import "github.com/zitadel/zitadel/internal/eventstore"
|
||||
|
||||
func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, CreatedType, eventstore.GenericEventMapper[CreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, UpdatedType, eventstore.GenericEventMapper[UpdatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, DeactivatedType, eventstore.GenericEventMapper[DeactivatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, ReactivatedType, eventstore.GenericEventMapper[ReactivatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, DeletedType, eventstore.GenericEventMapper[DeletedEvent])
|
||||
}
|
233
internal/repository/user/schema/schema.go
Normal file
233
internal/repository/user/schema/schema.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
eventPrefix = "user_schema."
|
||||
CreatedType = eventPrefix + "created"
|
||||
UpdatedType = eventPrefix + "updated"
|
||||
DeactivatedType = eventPrefix + "deactivated"
|
||||
ReactivatedType = eventPrefix + "reactivated"
|
||||
DeletedType = eventPrefix + "deleted"
|
||||
|
||||
uniqueSchemaType = "user_schema_type"
|
||||
)
|
||||
|
||||
func NewAddSchemaTypeUniqueConstraint(schemaType string) *eventstore.UniqueConstraint {
|
||||
return eventstore.NewAddEventUniqueConstraint(
|
||||
uniqueSchemaType,
|
||||
schemaType,
|
||||
"Errors.UserSchema.Type.AlreadyExists")
|
||||
}
|
||||
|
||||
func NewRemoveSchemaTypeUniqueConstraint(schemaType string) *eventstore.UniqueConstraint {
|
||||
return eventstore.NewRemoveUniqueConstraint(
|
||||
uniqueSchemaType,
|
||||
schemaType,
|
||||
)
|
||||
}
|
||||
|
||||
type CreatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
SchemaType string `json:"schemaType"`
|
||||
Schema json.RawMessage `json:"schema,omitempty"`
|
||||
PossibleAuthenticators []domain.AuthenticatorType `json:"possibleAuthenticators,omitempty"`
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return []*eventstore.UniqueConstraint{NewAddSchemaTypeUniqueConstraint(e.SchemaType)}
|
||||
}
|
||||
|
||||
func NewCreatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
|
||||
schemaType string,
|
||||
schema json.RawMessage,
|
||||
possibleAuthenticators []domain.AuthenticatorType,
|
||||
) *CreatedEvent {
|
||||
return &CreatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
CreatedType,
|
||||
),
|
||||
SchemaType: schemaType,
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: possibleAuthenticators,
|
||||
}
|
||||
}
|
||||
|
||||
type UpdatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
SchemaType *string `json:"schemaType,omitempty"`
|
||||
Schema json.RawMessage `json:"schema,omitempty"`
|
||||
PossibleAuthenticators []domain.AuthenticatorType `json:"possibleAuthenticators,omitempty"`
|
||||
oldSchemaType string
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
if e.oldSchemaType == "" {
|
||||
return nil
|
||||
}
|
||||
return []*eventstore.UniqueConstraint{
|
||||
NewRemoveSchemaTypeUniqueConstraint(e.oldSchemaType),
|
||||
NewAddSchemaTypeUniqueConstraint(*e.SchemaType),
|
||||
}
|
||||
}
|
||||
|
||||
func NewUpdatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []Changes,
|
||||
) *UpdatedEvent {
|
||||
updatedEvent := &UpdatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
UpdatedType,
|
||||
),
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(updatedEvent)
|
||||
}
|
||||
return updatedEvent
|
||||
}
|
||||
|
||||
type Changes func(event *UpdatedEvent)
|
||||
|
||||
func ChangeSchemaType(oldSchemaType, schemaType string) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaType = &schemaType
|
||||
e.oldSchemaType = oldSchemaType
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSchema(schema json.RawMessage) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.Schema = schema
|
||||
}
|
||||
}
|
||||
|
||||
func ChangePossibleAuthenticators(possibleAuthenticators []domain.AuthenticatorType) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.PossibleAuthenticators = possibleAuthenticators
|
||||
}
|
||||
}
|
||||
|
||||
type DeactivatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *DeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDeactivatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *DeactivatedEvent {
|
||||
return &DeactivatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
DeactivatedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type ReactivatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *ReactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *ReactivatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *ReactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewReactivatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *ReactivatedEvent {
|
||||
return &ReactivatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ReactivatedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type DeletedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
schemaType string
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return []*eventstore.UniqueConstraint{
|
||||
NewRemoveSchemaTypeUniqueConstraint(e.schemaType),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDeletedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
schemaType string,
|
||||
) *DeletedEvent {
|
||||
return &DeletedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
DeletedType,
|
||||
),
|
||||
schemaType: schemaType,
|
||||
}
|
||||
}
|
@@ -563,6 +563,16 @@ Errors:
|
||||
NotFound: Изпълнението не е намерено
|
||||
IncludeNotFound: Включването не е намерено
|
||||
NoTargets: Няма определени цели
|
||||
UserSchema:
|
||||
NotEnabled: Функцията „Потребителска схема“ не е активирана
|
||||
Type:
|
||||
Missing: Липсва тип потребителска схема
|
||||
AlreadyExists: Типът потребителска схема вече съществува
|
||||
Authenticator:
|
||||
Invalid: Невалиден тип удостоверител
|
||||
NotActive: Потребителската схема не е активна
|
||||
NotInactive: Потребителската схема не е неактивна
|
||||
NotExists: Потребителската схема не съществува
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
@@ -576,6 +586,7 @@ AggregateTypes:
|
||||
feature: Особеност
|
||||
target: Целта
|
||||
execution: Екзекуция
|
||||
user_schema: Потребителска схема
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1268,6 +1279,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Паролата на SMTP конфигурацията е променена
|
||||
removed: Премахната SMTP конфигурация
|
||||
user_schema:
|
||||
created: Създадена е потребителска схема
|
||||
updated: Потребителската схема е актуализирана
|
||||
deactivated: Потребителската схема е деактивирана
|
||||
reactivated: Потребителската схема е активирана отново
|
||||
deleted: Потребителската схема е изтрита
|
||||
Application:
|
||||
OIDC:
|
||||
UnsupportedVersion: Вашата OIDC версия не се поддържа
|
||||
|
@@ -543,6 +543,16 @@ Errors:
|
||||
NotFound: Provedení nenalezeno
|
||||
IncludeNotFound: Zahrnout nenalezeno
|
||||
NoTargets: Nejsou definovány žádné cíle
|
||||
UserSchema:
|
||||
NotEnabled: Funkce "Uživatelské schéma" není povolena
|
||||
Type:
|
||||
Missing: Chybí typ uživatelského schématu
|
||||
AlreadyExists: Typ uživatelského schématu již existuje
|
||||
Authenticator:
|
||||
Invalid: Neplatný typ ověřovače
|
||||
NotActive: Uživatelské schéma není aktivní
|
||||
NotInactive: Uživatelské schéma není neaktivní
|
||||
NotExists: Uživatelské schéma neexistuje
|
||||
|
||||
AggregateTypes:
|
||||
action: Akce
|
||||
@@ -556,6 +566,7 @@ AggregateTypes:
|
||||
feature: Funkce
|
||||
target: Cíl
|
||||
execution: Provedení
|
||||
user_schema: Uživatelské schéma
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1233,6 +1244,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Heslo konfigurace SMTP změněno
|
||||
removed: Konfigurace SMTP odstraněna
|
||||
user_schema:
|
||||
created: Vytvořeno uživatelské schéma
|
||||
updated: Uživatelské schéma bylo aktualizováno
|
||||
deactivated: Uživatelské schéma deaktivováno
|
||||
reactivated: Uživatelské schéma bylo znovu aktivováno
|
||||
deleted: Uživatelské schéma bylo smazáno
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Ausführung nicht gefunden
|
||||
IncludeNotFound: Einschließen nicht gefunden
|
||||
NoTargets: Keine Ziele definiert
|
||||
UserSchema:
|
||||
NotEnabled: Funktion Benutzerschema ist nicht aktiviert
|
||||
Type:
|
||||
Missing: Benutzerschematyp fehlt
|
||||
AlreadyExists: Benutzerschematyp existiert bereits
|
||||
Authenticator:
|
||||
Invalid: Ungültiger Authentifizierungstyp
|
||||
NotActive: Benutzerschema nicht aktiv
|
||||
NotInactive: Benutzerschema nicht inaktiv
|
||||
NotExists: Benutzerschema existiert nicht
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Feature
|
||||
target: Ziel
|
||||
execution: Ausführung
|
||||
user_schema: Benutzerschema
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1236,6 +1247,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Passwort von SMTP Konfiguration geändert
|
||||
removed: SMTP Konfiguration gelöscht
|
||||
user_schema:
|
||||
created: Benutzerschema erstellt
|
||||
updated: Benutzerschema geändert
|
||||
deactivated: Benutzerschema deaktiviert
|
||||
reactivated: Benutzerschema reaktiviert
|
||||
deleted: Benutzerschema gelöscht
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Execution not found
|
||||
IncludeNotFound: Include not found
|
||||
NoTargets: No targets defined
|
||||
UserSchema:
|
||||
NotEnabled: Feature "User Schema" is not enabled
|
||||
Type:
|
||||
Missing: User Schema Type missing
|
||||
AlreadyExists: User Schema Type already exists
|
||||
Authenticator:
|
||||
Invalid: Invalid authenticator type
|
||||
NotActive: User Schema not active
|
||||
NotInactive: User Schema not inactive
|
||||
NotExists: User Schema does not exist
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Feature
|
||||
target: Target
|
||||
execution: Execution
|
||||
user_schema: User Schema
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1236,6 +1247,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Password of SMTP configuration changed
|
||||
removed: SMTP configuration removed
|
||||
user_schema:
|
||||
created: User Schema created
|
||||
updated: User Schema updated
|
||||
deactivated: User Schema deactivated
|
||||
reactivated: User Schema reactivated
|
||||
deleted: User Schema deleted
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Ejecución no encontrada
|
||||
IncludeNotFound: Incluir no encontrado
|
||||
NoTargets: No hay objetivos definidos
|
||||
UserSchema:
|
||||
NotEnabled: La función "Esquema de usuario" no está habilitada
|
||||
Type:
|
||||
Missing: Falta el tipo de esquema de usuario
|
||||
AlreadyExists: El tipo de esquema de usuario ya existe
|
||||
Authenticator:
|
||||
Invalid: Tipo de autenticador no válido
|
||||
NotActive: Esquema de usuario no activo
|
||||
NotInactive: Esquema de usuario no inactivo
|
||||
NotExists: El esquema de usuario no existe
|
||||
|
||||
AggregateTypes:
|
||||
action: Acción
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Característica
|
||||
target: Objectivo
|
||||
execution: Ejecución
|
||||
user_schema: Esquema de usuario
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1236,6 +1247,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Contraseña de configuración SMTP modificada
|
||||
removed: Configuración SMTP eliminada
|
||||
user_schema:
|
||||
created: Esquema de usuario creado
|
||||
updated: Esquema de usuario actualizado
|
||||
deactivated: Esquema de usuario desactivado
|
||||
reactivated: Esquema de usuario reactivado
|
||||
deleted: Esquema de usuario eliminado
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Exécution introuvable
|
||||
IncludeNotFound: Inclure introuvable
|
||||
NoTargets: Aucune cible définie
|
||||
UserSchema:
|
||||
NotEnabled: La fonctionnalité "Schéma utilisateur" n'est pas activée
|
||||
Type:
|
||||
Missing: Type de schéma utilisateur manquant
|
||||
AlreadyExists: Le type de schéma utilisateur existe déjà
|
||||
Authenticator:
|
||||
Invalid: Type d'authentificateur invalide
|
||||
NotActive: Schéma utilisateur non actif
|
||||
NotInactive: Le schéma utilisateur n'est pas inactif
|
||||
NotExists: Le schéma utilisateur n'existe pas
|
||||
|
||||
AggregateTypes:
|
||||
action: Action
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Fonctionnalité
|
||||
target: Cible
|
||||
execution: Exécution
|
||||
user_schema: Schéma utilisateur
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1065,6 +1076,12 @@ EventTypes:
|
||||
deactivated: Action désactivée
|
||||
reactivated: Action réactivée
|
||||
removed: Action supprimée
|
||||
user_schema:
|
||||
created: Schéma utilisateur créé
|
||||
updated: Schéma utilisateur mis à jour
|
||||
deactivated: Schéma utilisateur désactivé
|
||||
reactivated: Schéma utilisateur réactivé
|
||||
deleted: Schéma utilisateur supprimé
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -547,7 +547,16 @@ Errors:
|
||||
NotFound: Esecuzione non trovata
|
||||
IncludeNotFound: Includi non trovato
|
||||
NoTargets: Nessun obiettivo definito
|
||||
|
||||
UserSchema:
|
||||
NotEnabled: La funzionalità "Schema utente" non è abilitata
|
||||
Type:
|
||||
Missing: Tipo di schema utente mancante
|
||||
AlreadyExists: Il tipo di schema utente esiste già
|
||||
Authenticator:
|
||||
Invalid: Tipo di autenticatore non valido
|
||||
NotActive: Schema utente non attivo
|
||||
NotInactive: Schema utente non inattivo
|
||||
NotExists: Lo schema utente non esiste
|
||||
|
||||
AggregateTypes:
|
||||
action: Azione
|
||||
@@ -561,6 +570,7 @@ AggregateTypes:
|
||||
feature: Funzionalità
|
||||
target: Bersaglio
|
||||
execution: Esecuzione
|
||||
user_schema: Schema utente
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1067,6 +1077,12 @@ EventTypes:
|
||||
deactivated: Azione disattivata
|
||||
reactivated: Azione riattivata
|
||||
removed: Azione rimossa
|
||||
user_schema:
|
||||
created: Schema utente creato
|
||||
updated: Schema utente aggiornato
|
||||
deactivated: Schema utente disattivato
|
||||
reactivated: Schema utente riattivato
|
||||
deleted: Schema utente eliminato
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -535,6 +535,16 @@ Errors:
|
||||
NotFound: 実行が見つかりませんでした
|
||||
IncludeNotFound: 見つからないものを含める
|
||||
NoTargets: ターゲットが定義されていません
|
||||
UserSchema:
|
||||
NotEnabled: 機能「ユーザースキーマ」が有効になっていません
|
||||
Type:
|
||||
Missing: ユーザースキーマタイプがありません
|
||||
AlreadyExists: ユーザースキーマタイプはすでに存在します
|
||||
Authenticator:
|
||||
Invalid: 無効な認証子のタイプ
|
||||
NotActive: ユーザースキーマがアクティブではありません
|
||||
NotInactive: ユーザースキーマが非アクティブではありません
|
||||
NotExists: ユーザースキーマが存在しません
|
||||
|
||||
AggregateTypes:
|
||||
action: アクション
|
||||
@@ -548,6 +558,7 @@ AggregateTypes:
|
||||
feature: 特徴
|
||||
target: 目標
|
||||
execution: 実行
|
||||
user_schema: ユーザースキーマ
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1225,6 +1236,12 @@ EventTypes:
|
||||
password:
|
||||
changed: SMTP構成パスワードの変更
|
||||
removed: SMTP構成の削除
|
||||
user_schema:
|
||||
created: ーザースキーマが作成されました
|
||||
updated: ユーザースキーマが更新されました
|
||||
deactivated: ユーザースキーマが非アクティブ化されました
|
||||
reactivated: ユーザースキーマが再アクティブ化されました
|
||||
deleted: ユーザースキーマが削除されました
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -545,6 +545,16 @@ Errors:
|
||||
NotFound: Извршувањето не е пронајдено
|
||||
IncludeNotFound: Вклучете не е пронајден
|
||||
NoTargets: Не се дефинирани цели
|
||||
UserSchema:
|
||||
NotEnabled: Функцијата „Корисничка шема“ не е овозможена
|
||||
Type:
|
||||
Missing: Недостасува тип на корисничка шема
|
||||
AlreadyExists: Тип на корисничка шема веќе постои
|
||||
Authenticator:
|
||||
Invalid: Неважечки тип на автентикатор
|
||||
NotActive: Корисничката шема не е активна
|
||||
NotInactive: Корисничката шема не е неактивна
|
||||
NotExists: Корисничката шема не постои
|
||||
|
||||
AggregateTypes:
|
||||
action: Акција
|
||||
@@ -558,6 +568,7 @@ AggregateTypes:
|
||||
feature: Карактеристика
|
||||
target: Цел
|
||||
execution: Извршување
|
||||
user_schema: Корисничка шема
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1234,6 +1245,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Променета лозинка на SMTP конфигурацијата
|
||||
removed: Отстранета SMTP конфигурација
|
||||
user_schema:
|
||||
created: Создадена е корисничка шема
|
||||
updated: Корисничката шема е ажурирана
|
||||
deactivated: Корисничката шема е деактивирана
|
||||
reactivated: Корисничката шема е реактивирана
|
||||
deleted: Корисничката шема е избришана
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Uitvoering niet gevonden
|
||||
IncludeNotFound: Inclusief niet gevonden
|
||||
NoTargets: Geen doelstellingen gedefinieerd
|
||||
UserSchema:
|
||||
NotEnabled: Functie "Gebruikersschema" is niet ingeschakeld
|
||||
Type:
|
||||
Missing: Type gebruikersschema ontbreekt
|
||||
AlreadyExists: Type gebruikersschema bestaat al
|
||||
Authenticator:
|
||||
Invalid: Ongeldig authenticatortype
|
||||
NotActive: Gebruikersschema niet actief
|
||||
NotInactive: Gebruikersschema niet inactief
|
||||
NotExists: Gebruikersschema bestaat niet
|
||||
|
||||
AggregateTypes:
|
||||
action: Actie
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Functie
|
||||
target: Doel
|
||||
execution: Executie
|
||||
user_schema: Gebruikersschema
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1236,6 +1247,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Wachtwoord van SMTP-configuratie gewijzigd
|
||||
removed: SMTP-configuratie verwijderd
|
||||
user_schema:
|
||||
created: Gebruikersschema gemaakt
|
||||
updated: Gebruikersschema bijgewerkt
|
||||
deactivated: Gebruikersschema gedeactiveerd
|
||||
reactivated: Gebruikersschema opnieuw geactiveerd
|
||||
deleted: Gebruikersschema verwijderd
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: Nie znaleziono wykonania
|
||||
IncludeNotFound: Nie znaleziono uwzględnienia
|
||||
NoTargets: Nie zdefiniowano celów
|
||||
UserSchema:
|
||||
NotEnabled: Funkcja „Schemat użytkownika” nie jest włączona
|
||||
Type:
|
||||
Missing: Brak typu schematu użytkownika
|
||||
AlreadyExists: Typ schematu użytkownika już istnieje
|
||||
Authenticator:
|
||||
Invalid: Nieprawidłowy typ uwierzytelnienia
|
||||
NotActive: Schemat użytkownika nieaktywny
|
||||
NotInactive: Schemat użytkownika nie jest nieaktywny
|
||||
NotExists: Schemat użytkownika nie istnieje
|
||||
|
||||
AggregateTypes:
|
||||
action: Działanie
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: Funkcja
|
||||
target: Cel
|
||||
execution: Wykonanie
|
||||
user_schema: Schemat użytkownika
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1236,6 +1247,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Hasło konfiguracji SMTP zmienione
|
||||
removed: Konfiguracja SMTP usunięta
|
||||
user_schema:
|
||||
created: Utworzono schemat użytkownika
|
||||
updated: Schemat użytkownika zaktualizowany
|
||||
deactivated: Schemat użytkownika dezaktywowany
|
||||
reactivated: Schemat użytkownika został ponownie aktywowany
|
||||
deleted: Schemat użytkownika został usunięty
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -540,6 +540,16 @@ Errors:
|
||||
NotFound: Execução não encontrada
|
||||
IncludeNotFound: Incluir não encontrado
|
||||
NoTargets: Nenhuma meta definida
|
||||
UserSchema:
|
||||
NotEnabled: O recurso "Esquema do usuário" não está habilitado
|
||||
Type:
|
||||
Missing: Tipo de esquema de usuário ausente
|
||||
AlreadyExists: O tipo de esquema de usuário já existe
|
||||
Authenticator:
|
||||
Invalid: Tipo de autenticador inválido
|
||||
NotActive: Esquema do usuário não ativo
|
||||
NotInactive: Esquema do usuário não inativo
|
||||
NotExists: O esquema do usuário não existe
|
||||
|
||||
AggregateTypes:
|
||||
action: Ação
|
||||
@@ -553,6 +563,7 @@ AggregateTypes:
|
||||
feature: Recurso
|
||||
target: Objetivo
|
||||
execution: Execução
|
||||
user_schema: Esquema do usuário
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1230,6 +1241,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Senha da configuração SMTP alterada
|
||||
removed: Configuração SMTP removida
|
||||
user_schema:
|
||||
created: Esquema de usuário criado
|
||||
updated: Esquema do usuário atualizado
|
||||
deactivated: Esquema de usuário desativado
|
||||
reactivated: Esquema do usuário reativado
|
||||
deleted: Esquema do usuário excluído
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
@@ -534,6 +534,16 @@ Errors:
|
||||
NotFound: Исполнение не найдено
|
||||
IncludeNotFound: Включить не найдено
|
||||
NoTargets: Цели не определены
|
||||
UserSchema:
|
||||
NotEnabled: Функция «Пользовательская схема» не включена
|
||||
Type:
|
||||
Missing: Тип пользовательской схемы отсутствует
|
||||
AlreadyExists: Тип пользовательской схемы уже существует
|
||||
Authenticator:
|
||||
Invalid: Неверный тип аутентификатора
|
||||
NotActive: Пользовательская схема не активна
|
||||
NotInactive: Пользовательская схема не неактивна
|
||||
NotExists: Пользовательская схема не существует
|
||||
|
||||
AggregateTypes:
|
||||
action: Действие
|
||||
@@ -547,6 +557,7 @@ AggregateTypes:
|
||||
feature: Особенность
|
||||
target: мишень
|
||||
execution: Исполнение
|
||||
user_schema: Пользовательская схема
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1224,6 +1235,12 @@ EventTypes:
|
||||
password:
|
||||
changed: Изменен пароль конфигурации SMTP
|
||||
removed: Удалена конфигурация SMTP
|
||||
user_schema:
|
||||
created: Пользовательская схема создана
|
||||
updated: Пользовательская схема обновлена
|
||||
deactivated: Пользовательская схема деактивирована
|
||||
reactivated: Пользовательская схема повторно активирована
|
||||
deleted: Пользовательская схема удалена
|
||||
Application:
|
||||
OIDC:
|
||||
UnsupportedVersion: Ваша версия OIDC не поддерживается
|
||||
|
@@ -546,6 +546,16 @@ Errors:
|
||||
NotFound: 未找到执行
|
||||
IncludeNotFound: 包括未找到的内容
|
||||
NoTargets: 没有定义目标
|
||||
UserSchema:
|
||||
NotEnabled: 未启用“用户架构”功能
|
||||
Type:
|
||||
Missing: 缺少用户架构类型
|
||||
AlreadyExists: 用户架构类型已存在
|
||||
Authenticator:
|
||||
Invalid: 验证器类型无效
|
||||
NotActive: 用户架构未激活
|
||||
NotInactive: 用户架构未处于非活动状态
|
||||
NotExists: 用户架构不存在
|
||||
|
||||
AggregateTypes:
|
||||
action: 动作
|
||||
@@ -559,6 +569,7 @@ AggregateTypes:
|
||||
feature: 特征
|
||||
target: 靶
|
||||
execution: 执行
|
||||
user_schema: 用户模式
|
||||
|
||||
EventTypes:
|
||||
execution:
|
||||
@@ -1065,6 +1076,12 @@ EventTypes:
|
||||
deactivated: 停用动作
|
||||
reactivated: 启用动作
|
||||
removed: 删除动作
|
||||
user_schema:
|
||||
created: 已创建用户架构
|
||||
updated: 用户架构已更新
|
||||
deactivated: 用户架构已停用
|
||||
reactivated: 用户架构已重新激活
|
||||
deleted: 用户架构已删除
|
||||
|
||||
Application:
|
||||
OIDC:
|
||||
|
Reference in New Issue
Block a user