diff --git a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go index 2bdbdee06..169ee0e5d 100644 --- a/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go +++ b/internal/api/grpc/resources/action/v3alpha/integration_test/execution_target_test.go @@ -250,16 +250,26 @@ func TestServer_ExecutionTarget(t *testing.T) { require.NoError(t, err) defer close() } - - got, err := instance.Client.ActionV3Alpha.GetTarget(tt.ctx, tt.req) - if tt.wantErr { - require.Error(t, err) - return + retryDuration := 5 * time.Second + if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok { + retryDuration = time.Until(ctxDeadline) } - require.NoError(t, err) - integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails()) - assert.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig()) + require.EventuallyWithT(t, func(ttt *assert.CollectT) { + got, err := instance.Client.ActionV3Alpha.GetTarget(tt.ctx, tt.req) + if tt.wantErr { + assert.Error(ttt, err, "Error: "+err.Error()) + } else { + assert.NoError(ttt, err) + } + if err != nil { + return + } + + integration.AssertResourceDetails(t, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails()) + assert.Equal(t, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig()) + }, retryDuration, time.Millisecond*100, "timeout waiting for expected execution result") + if tt.clean != nil { tt.clean(tt.ctx) } diff --git a/internal/api/grpc/resources/object/v3alpha/converter.go b/internal/api/grpc/resources/object/v3alpha/converter.go index c2dc7bcc6..869311b92 100644 --- a/internal/api/grpc/resources/object/v3alpha/converter.go +++ b/internal/api/grpc/resources/object/v3alpha/converter.go @@ -75,3 +75,18 @@ func SearchQueryPbToQuery(defaults systemdefaults.SystemDefaults, query *resourc } return offset, limit, asc, nil } + +func ResourceOwnerFromOrganization(organization *object.Organization) string { + if organization == nil { + return "" + } + + if organization.GetOrgId() != "" { + return organization.GetOrgId() + } + if organization.GetOrgDomain() != "" { + // TODO get org from domain + return "" + } + return "" +} diff --git a/internal/api/grpc/resources/user/v3alpha/integration_test/server_test.go b/internal/api/grpc/resources/user/v3alpha/integration_test/server_test.go index fa82005eb..b2b11fd51 100644 --- a/internal/api/grpc/resources/user/v3alpha/integration_test/server_test.go +++ b/internal/api/grpc/resources/user/v3alpha/integration_test/server_test.go @@ -14,50 +14,41 @@ import ( "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/feature/v2" - user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha" ) var ( - IAMOwnerCTX, SystemCTX context.Context - UserCTX context.Context - Instance *integration.Instance - Client user.ZITADELUsersClient + CTX context.Context ) func TestMain(m *testing.M) { os.Exit(func() int { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() - - Instance = integration.NewInstance(ctx) - - IAMOwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner) - SystemCTX = integration.WithSystemAuthorization(ctx) - UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeLogin) - Client = Instance.Client.UserV3Alpha + CTX = ctx return m.Run() }()) } -func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) { - f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ +func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) { + ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(t, err) if f.UserSchema.GetEnabled() { return } - _, err = Instance.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{ + _, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{ UserSchema: gu.Ptr(true), }) require.NoError(t, err) retryDuration := time.Minute - if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok { + if ctxDeadline, ok := ctx.Deadline(); ok { retryDuration = time.Until(ctxDeadline) } require.EventuallyWithT(t, func(ttt *assert.CollectT) { - f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ + f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(ttt, err) diff --git a/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go b/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go index 7f15a2c56..c8db0f7f6 100644 --- a/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go +++ b/internal/api/grpc/resources/user/v3alpha/integration_test/user_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/brianvoe/gofakeit/v6" + "github.com/muhlemmer/gu" "github.com/stretchr/testify/require" "github.com/zitadel/logging" "google.golang.org/protobuf/types/known/structpb" @@ -19,7 +20,11 @@ import ( ) func TestServer_CreateUser(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ "$schema": "urn:zitadel:schema:v1", "type": "object", @@ -29,7 +34,7 @@ func TestServer_CreateUser(t *testing.T) { } } }`) - schemaResp := Instance.CreateUserSchema(IAMOwnerCTX, schema) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) permissionSchema := []byte(`{ "$schema": "urn:zitadel:schema:v1", "type": "object", @@ -43,8 +48,8 @@ func TestServer_CreateUser(t *testing.T) { } } }`) - permissionSchemaResp := Instance.CreateUserSchema(IAMOwnerCTX, permissionSchema) - orgResp := Instance.CreateOrganization(IAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + permissionSchemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, permissionSchema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) type res struct { want *resource_object.Details @@ -60,7 +65,7 @@ func TestServer_CreateUser(t *testing.T) { }{ { name: "user create, no schemaID", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -89,7 +94,7 @@ func TestServer_CreateUser(t *testing.T) { }, { name: "user create, no permission", - ctx: UserCTX, + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -105,7 +110,7 @@ func TestServer_CreateUser(t *testing.T) { }, { name: "user create, invalid schema permission, owner", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -121,7 +126,7 @@ func TestServer_CreateUser(t *testing.T) { }, { name: "user create, no user data", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -144,7 +149,7 @@ func TestServer_CreateUser(t *testing.T) { }, { name: "user create, ok", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -165,9 +170,10 @@ func TestServer_CreateUser(t *testing.T) { }, }, }, - }, { + }, + { name: "user create, full contact, ok", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.CreateUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ @@ -204,7 +210,423 @@ func TestServer_CreateUser(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Instance.Client.UserV3Alpha.CreateUser(tt.ctx, tt.req) + got, err := instance.Client.UserV3Alpha.CreateUser(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertResourceDetails(t, tt.res.want, got.Details) + if tt.res.returnCodeEmail { + require.NotNil(t, got.EmailCode) + } + if tt.res.returnCodePhone { + require.NotNil(t, got.PhoneCode) + } + }) + } +} + +func TestServer_PatchUser(t *testing.T) { + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + + schema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + permissionSchema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "urn:zitadel:schema:permission": { + "owner": "r", + "self": "r" + }, + "type": "string" + } + } + }`) + permissionSchemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, permissionSchema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + + type res struct { + want *resource_object.Details + returnCodeEmail bool + returnCodePhone bool + } + tests := []struct { + name string + ctx context.Context + dep func(req *user.PatchUserRequest) error + req *user.PatchUserRequest + res res + wantErr bool + }{ + { + name: "user patch, no context", + ctx: context.Background(), + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"user\"}"), + }, + }, + wantErr: true, + }, + { + name: "user patch, no permission", + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"user\"}"), + }, + }, + wantErr: true, + }, + { + name: "user patch, invalid schema permission, owner", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + SchemaId: gu.Ptr(permissionSchemaResp.GetDetails().GetId()), + Data: unmarshalJSON("{\"name\": \"user\"}"), + }, + }, + wantErr: true, + }, + { + name: "user patch, not found", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "notexisting", + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"user\"}"), + }, + }, + wantErr: true, + }, + { + name: "user patch, not found, org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "not existing", + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"user\"}"), + }, + }, + wantErr: true, + }, + { + name: "user patch, no change", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + data := "{\"name\": \"user\"}" + schemaID := schemaResp.GetDetails().GetId() + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data)) + req.Id = userResp.GetDetails().GetId() + req.User.Data = unmarshalJSON(data) + req.User.SchemaId = gu.Ptr(schemaID) + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{}, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + }, + { + name: "user patch, schema, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + changedSchemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + req.User.SchemaId = gu.Ptr(changedSchemaResp.Details.Id) + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{}, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + }, + { + name: "user patch, schema and data, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + changedSchemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + req.User.SchemaId = gu.Ptr(changedSchemaResp.Details.Id) + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"changed\"}"), + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + }, + { + name: "user patch, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"changed\"}"), + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + }, + { + name: "user patch, no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"changed\"}"), + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + }, + { + name: "user patch, contact email, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Contact: &user.SetContact{ + Email: &user.SetEmail{ + Address: gofakeit.Email(), + Verification: &user.SetEmail_ReturnCode{ReturnCode: &user.ReturnEmailVerificationCode{}}, + }, + }, + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + returnCodeEmail: true, + }, + }, + { + name: "user patch, contact phone, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Contact: &user.SetContact{ + Phone: &user.SetPhone{ + Number: gofakeit.Phone(), + Verification: &user.SetPhone_ReturnCode{ReturnCode: &user.ReturnPhoneVerificationCode{}}, + }, + }, + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + returnCodePhone: true, + }, + }, + { + name: "user patch, full contact, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.PatchUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.PatchUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + User: &user.PatchUser{ + Data: unmarshalJSON("{\"name\": \"changed\"}"), + Contact: &user.SetContact{ + Email: &user.SetEmail{ + Address: gofakeit.Email(), + Verification: &user.SetEmail_ReturnCode{ReturnCode: &user.ReturnEmailVerificationCode{}}, + }, + Phone: &user.SetPhone{ + Number: gofakeit.Phone(), + Verification: &user.SetPhone_ReturnCode{ReturnCode: &user.ReturnPhoneVerificationCode{}}, + }, + }, + }, + }, + res: res{ + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + returnCodePhone: true, + returnCodeEmail: true, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + err := tt.dep(tt.req) + require.NoError(t, err) + } + got, err := instance.Client.UserV3Alpha.PatchUser(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return @@ -222,7 +644,11 @@ func TestServer_CreateUser(t *testing.T) { } func TestServer_DeleteUser(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ "$schema": "urn:zitadel:schema:v1", "type": "object", @@ -232,49 +658,66 @@ func TestServer_DeleteUser(t *testing.T) { } } }`) - schemaResp := Instance.CreateUserSchema(IAMOwnerCTX, schema) - orgResp := Instance.CreateOrganization(IAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) tests := []struct { name string ctx context.Context - dep func(ctx context.Context, req *user.DeleteUserRequest) error + dep func(req *user.DeleteUserRequest) error req *user.DeleteUserRequest want *resource_object.Details wantErr bool }{ { name: "user delete, no userID", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.DeleteUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ OrgId: orgResp.GetOrganizationId(), }, }, - UserId: "", + Id: "", }, wantErr: true, }, { name: "user delete, not existing", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &user.DeleteUserRequest{ Organization: &object.Organization{ Property: &object.Organization_OrgId{ OrgId: orgResp.GetOrganizationId(), }, }, - UserId: "notexisting", + Id: "notexisting", + }, + wantErr: true, + }, + { + name: "user delete, not existing, org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeleteUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "notexisting", + }, + }, }, wantErr: true, }, { name: "user delete, no context", ctx: context.Background(), - dep: func(ctx context.Context, req *user.DeleteUserRequest) error { - userResp := Instance.CreateSchemaUser(IAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) - req.UserId = userResp.GetDetails().GetId() + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() return nil }, req: &user.DeleteUserRequest{ @@ -288,10 +731,10 @@ func TestServer_DeleteUser(t *testing.T) { }, { name: "user delete, no permission", - ctx: UserCTX, - dep: func(ctx context.Context, req *user.DeleteUserRequest) error { - userResp := Instance.CreateSchemaUser(IAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) - req.UserId = userResp.GetDetails().GetId() + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() return nil }, req: &user.DeleteUserRequest{ @@ -305,10 +748,75 @@ func TestServer_DeleteUser(t *testing.T) { }, { name: "user delete, ok", - ctx: IAMOwnerCTX, - dep: func(ctx context.Context, req *user.DeleteUserRequest) error { - userResp := Instance.CreateSchemaUser(ctx, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) - req.UserId = userResp.GetDetails().GetId() + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeleteUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user delete, no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeleteUserRequest{}, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user delete, locked, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.DeleteUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user delete, deactivated, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeleteUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) return nil }, req: &user.DeleteUserRequest{ @@ -330,10 +838,10 @@ func TestServer_DeleteUser(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.dep != nil { - err := tt.dep(tt.ctx, tt.req) + err := tt.dep(tt.req) require.NoError(t, err) } - got, err := Instance.Client.UserV3Alpha.DeleteUser(tt.ctx, tt.req) + got, err := instance.Client.UserV3Alpha.DeleteUser(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return @@ -352,3 +860,773 @@ func unmarshalJSON(data string) *structpb.Struct { } return user } + +func TestServer_LockUser(t *testing.T) { + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + + tests := []struct { + name string + ctx context.Context + dep func(req *user.LockUserRequest) error + req *user.LockUserRequest + want *resource_object.Details + wantErr bool + }{ + { + name: "user lock, no userID", + ctx: isolatedIAMOwnerCTX, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "", + }, + wantErr: true, + }, + { + name: "user lock, not existing", + ctx: isolatedIAMOwnerCTX, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "notexisting", + }, + wantErr: true, + }, + { + name: "user lock, not existing in org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "notexisting", + }, + }, + }, + wantErr: true, + }, + { + name: "user lock, no context", + ctx: context.Background(), + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user lock, no permission", + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user lock, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user lock, no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.LockUserRequest{}, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user lock, already locked", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user lock, deactivated", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.LockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.LockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + err := tt.dep(tt.req) + require.NoError(t, err) + } + got, err := instance.Client.UserV3Alpha.LockUser(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) + }) + } +} + +func TestServer_UnlockUser(t *testing.T) { + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + + tests := []struct { + name string + ctx context.Context + dep func(req *user.UnlockUserRequest) error + req *user.UnlockUserRequest + want *resource_object.Details + wantErr bool + }{ + { + name: "user unlock, no userID", + ctx: isolatedIAMOwnerCTX, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "", + }, + wantErr: true, + }, + { + name: "user unlock, not existing", + ctx: isolatedIAMOwnerCTX, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "notexisting", + }, + wantErr: true, + }, + { + name: "user unlock, not existing in org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "notexisting", + }, + }, + }, + wantErr: true, + }, + { + name: "user unlock, no context", + ctx: context.Background(), + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user unlock, no permission", + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user unlock, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user unlock,no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.UnlockUserRequest{}, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user unlock, already unlocked", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.UnlockUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + instance.UnlockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.UnlockUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + err := tt.dep(tt.req) + require.NoError(t, err) + } + got, err := instance.Client.UserV3Alpha.UnlockUser(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) + }) + } +} + +func TestServer_DeactivateUser(t *testing.T) { + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + + tests := []struct { + name string + ctx context.Context + dep func(req *user.DeactivateUserRequest) error + req *user.DeactivateUserRequest + want *resource_object.Details + wantErr bool + }{ + { + name: "user deactivate, no userID", + ctx: isolatedIAMOwnerCTX, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "", + }, + wantErr: true, + }, + { + name: "user deactivate, not existing", + ctx: isolatedIAMOwnerCTX, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "notexisting", + }, + wantErr: true, + }, + { + name: "user deactivate, not existing in org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "notexisting", + }, + }, + }, + wantErr: true, + }, + { + name: "user deactivate, no context", + ctx: context.Background(), + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user deactivate, no permission", + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user deactivate, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user deactivate, no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.DeactivateUserRequest{}, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user deactivate, already deactivated", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user deactivate, locked", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.DeactivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.LockSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.DeactivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + err := tt.dep(tt.req) + require.NoError(t, err) + } + got, err := instance.Client.UserV3Alpha.DeactivateUser(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) + }) + } +} + +func TestServer_ActivateUser(t *testing.T) { + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + schema := []byte(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`) + schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema) + orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email()) + + tests := []struct { + name string + ctx context.Context + dep func(req *user.ActivateUserRequest) error + req *user.ActivateUserRequest + want *resource_object.Details + wantErr bool + }{ + { + name: "user activate, no userID", + ctx: isolatedIAMOwnerCTX, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "", + }, + wantErr: true, + }, + { + name: "user activate, not existing", + ctx: isolatedIAMOwnerCTX, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + Id: "notexisting", + }, + wantErr: true, + }, + { + name: "user activate, not existing in org", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + return nil + }, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: "notexisting", + }, + }, + }, + wantErr: true, + }, + { + name: "user activate, no context", + ctx: context.Background(), + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user activate, no permission", + ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin), + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + { + name: "user activate, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user activate, no org, ok", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.ActivateUserRequest{}, + want: &resource_object.Details{ + Changed: timestamppb.Now(), + Owner: &object.Owner{ + Type: object.OwnerType_OWNER_TYPE_ORG, + Id: orgResp.GetOrganizationId(), + }, + }, + }, + { + name: "user activate, already activated", + ctx: isolatedIAMOwnerCTX, + dep: func(req *user.ActivateUserRequest) error { + userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}")) + req.Id = userResp.GetDetails().GetId() + instance.DeactivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + instance.ActivateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id) + return nil + }, + req: &user.ActivateUserRequest{ + Organization: &object.Organization{ + Property: &object.Organization_OrgId{ + OrgId: orgResp.GetOrganizationId(), + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.dep != nil { + err := tt.dep(tt.req) + require.NoError(t, err) + } + got, err := instance.Client.UserV3Alpha.ActivateUser(tt.ctx, tt.req) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + integration.AssertResourceDetails(t, tt.want, got.Details) + }) + } +} diff --git a/internal/api/grpc/resources/user/v3alpha/user.go b/internal/api/grpc/resources/user/v3alpha/user.go index ede2f122a..7c3f2a750 100644 --- a/internal/api/grpc/resources/user/v3alpha/user.go +++ b/internal/api/grpc/resources/user/v3alpha/user.go @@ -8,6 +8,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha" "github.com/zitadel/zitadel/internal/command" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/zerrors" object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha" @@ -26,7 +27,7 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_ return nil, err } return &user.CreateUserResponse{ - Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.ResourceOwner), + Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.Details.ResourceOwner), EmailCode: gu.Ptr(schemauser.ReturnCodeEmail), PhoneCode: gu.Ptr(schemauser.ReturnCodePhone), }, nil @@ -37,19 +38,35 @@ func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUs if err != nil { return nil, err } + return &command.CreateSchemaUser{ - ResourceOwner: authz.GetCtxData(ctx).OrgID, + ResourceOwner: organizationToCreateResourceOwner(ctx, req.Organization), SchemaID: req.GetUser().GetSchemaId(), ID: req.GetUser().GetUserId(), Data: data, }, nil } +func organizationToCreateResourceOwner(ctx context.Context, org *object.Organization) string { + resourceOwner := authz.GetCtxData(ctx).OrgID + if resourceOwnerReq := resource_object.ResourceOwnerFromOrganization(org); resourceOwnerReq != "" { + return resourceOwnerReq + } + return resourceOwner +} + +func organizationToUpdateResourceOwner(org *object.Organization) string { + if resourceOwnerReq := resource_object.ResourceOwnerFromOrganization(org); resourceOwnerReq != "" { + return resourceOwnerReq + } + return "" +} + func (s *Server) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (_ *user.DeleteUserResponse, err error) { if err := checkUserSchemaEnabled(ctx); err != nil { return nil, err } - details, err := s.command.DeleteSchemaUser(ctx, req.GetUserId()) + details, err := s.command.DeleteSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId()) if err != nil { return nil, err } @@ -64,3 +81,126 @@ func checkUserSchemaEnabled(ctx context.Context) error { } return zerrors.ThrowPreconditionFailed(nil, "TODO", "Errors.UserSchema.NotEnabled") } + +func (s *Server) PatchUser(ctx context.Context, req *user.PatchUserRequest) (_ *user.PatchUserResponse, err error) { + if err := checkUserSchemaEnabled(ctx); err != nil { + return nil, err + } + schemauser, err := patchUserRequestToChangeSchemaUser(req) + if err != nil { + return nil, err + } + + if err := s.command.ChangeSchemaUser(ctx, schemauser, s.userCodeAlg); err != nil { + return nil, err + } + return &user.PatchUserResponse{ + Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.Details.ResourceOwner), + EmailCode: gu.Ptr(schemauser.ReturnCodeEmail), + PhoneCode: gu.Ptr(schemauser.ReturnCodePhone), + }, nil +} + +func patchUserRequestToChangeSchemaUser(req *user.PatchUserRequest) (_ *command.ChangeSchemaUser, err error) { + var data []byte + if req.GetUser().Data != nil { + data, err = req.GetUser().GetData().MarshalJSON() + if err != nil { + return nil, err + } + } + + var email *command.Email + var phone *command.Phone + if req.GetUser().GetContact() != nil { + if req.GetUser().GetContact().GetEmail() != nil { + email = &command.Email{ + Address: domain.EmailAddress(req.GetUser().GetContact().Email.Address), + } + if req.GetUser().GetContact().Email.GetIsVerified() { + email.Verified = true + } + if req.GetUser().GetContact().Email.GetReturnCode() != nil { + email.ReturnCode = true + } + if req.GetUser().GetContact().Email.GetSendCode() != nil { + email.URLTemplate = req.GetUser().GetContact().Email.GetSendCode().GetUrlTemplate() + } + } + if req.GetUser().GetContact().Phone != nil { + phone = &command.Phone{ + Number: domain.PhoneNumber(req.GetUser().GetContact().Phone.Number), + } + if req.GetUser().GetContact().Phone.GetIsVerified() { + phone.Verified = true + } + if req.GetUser().GetContact().Phone.GetReturnCode() != nil { + phone.ReturnCode = true + } + } + } + return &command.ChangeSchemaUser{ + ResourceOwner: organizationToUpdateResourceOwner(req.Organization), + ID: req.GetId(), + SchemaID: req.GetUser().SchemaId, + Data: data, + Email: email, + Phone: phone, + }, nil +} + +func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) { + if err := checkUserSchemaEnabled(ctx); err != nil { + return nil, err + } + + details, err := s.command.DeactivateSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId()) + if err != nil { + return nil, err + } + return &user.DeactivateUserResponse{ + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), + }, nil +} + +func (s *Server) ActivateUser(ctx context.Context, req *user.ActivateUserRequest) (_ *user.ActivateUserResponse, err error) { + if err := checkUserSchemaEnabled(ctx); err != nil { + return nil, err + } + + details, err := s.command.ActivateSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId()) + if err != nil { + return nil, err + } + return &user.ActivateUserResponse{ + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), + }, nil +} + +func (s *Server) LockUser(ctx context.Context, req *user.LockUserRequest) (_ *user.LockUserResponse, err error) { + if err := checkUserSchemaEnabled(ctx); err != nil { + return nil, err + } + + details, err := s.command.LockSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId()) + if err != nil { + return nil, err + } + return &user.LockUserResponse{ + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), + }, nil +} + +func (s *Server) UnlockUser(ctx context.Context, req *user.UnlockUserRequest) (_ *user.UnlockUserResponse, err error) { + if err := checkUserSchemaEnabled(ctx); err != nil { + return nil, err + } + + details, err := s.command.UnlockSchemaUser(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId()) + if err != nil { + return nil, err + } + return &user.UnlockUserResponse{ + Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner), + }, nil +} diff --git a/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go b/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go index e7aaf081e..36cf03266 100644 --- a/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go +++ b/internal/api/grpc/resources/userschema/v3alpha/integration_test/query_test.go @@ -20,7 +20,10 @@ import ( ) func TestServer_ListUserSchemas(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) userSchema := new(structpb.Struct) err := userSchema.UnmarshalJSON([]byte(`{ @@ -43,7 +46,7 @@ func TestServer_ListUserSchemas(t *testing.T) { { name: "missing permission", args: args{ - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), req: &schema.SearchUserSchemasRequest{}, }, wantErr: true, @@ -51,7 +54,7 @@ func TestServer_ListUserSchemas(t *testing.T) { { name: "not found, error", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.SearchUserSchemasRequest{ Filters: []*schema.SearchFilter{ { @@ -75,11 +78,11 @@ func TestServer_ListUserSchemas(t *testing.T) { { name: "single (id), ok", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.SearchUserSchemasRequest{}, prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error { schemaType := gofakeit.Name() - createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType) + createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType) request.Filters = []*schema.SearchFilter{ { Filter: &schema.SearchFilter_IdFilter{ @@ -121,14 +124,14 @@ func TestServer_ListUserSchemas(t *testing.T) { { name: "multiple (type), ok", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.SearchUserSchemasRequest{}, prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error { schemaType := gofakeit.Name() schemaType1 := schemaType + "_1" schemaType2 := schemaType + "_2" - createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType1) - createResp2 := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType2) + createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType1) + createResp2 := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType2) request.SortingColumn = gu.Ptr(schema.FieldName_FIELD_NAME_TYPE) request.Query = &object.SearchQuery{Desc: false} @@ -186,12 +189,12 @@ func TestServer_ListUserSchemas(t *testing.T) { } retryDuration := 20 * time.Second - if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok { + if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok { retryDuration = time.Until(ctxDeadline) } require.EventuallyWithT(t, func(ttt *assert.CollectT) { - got, err := Client.SearchUserSchemas(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.SearchUserSchemas(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(ttt, err) return @@ -215,7 +218,10 @@ func TestServer_ListUserSchemas(t *testing.T) { } func TestServer_GetUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) userSchema := new(structpb.Struct) err := userSchema.UnmarshalJSON([]byte(`{ @@ -238,11 +244,11 @@ func TestServer_GetUserSchema(t *testing.T) { { name: "missing permission", args: args{ - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), req: &schema.GetUserSchemaRequest{}, prepare: func(request *schema.GetUserSchemaRequest, resp *schema.GetUserSchemaResponse) error { schemaType := gofakeit.Name() - createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType) + createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType) request.Id = createResp.GetDetails().GetId() return nil }, @@ -252,7 +258,7 @@ func TestServer_GetUserSchema(t *testing.T) { { name: "not existing, error", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.GetUserSchemaRequest{ Id: "notexisting", }, @@ -262,11 +268,11 @@ func TestServer_GetUserSchema(t *testing.T) { { name: "get, ok", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.GetUserSchemaRequest{}, prepare: func(request *schema.GetUserSchemaRequest, resp *schema.GetUserSchemaResponse) error { schemaType := gofakeit.Name() - createResp := Instance.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType) + createResp := instance.CreateUserSchemaEmptyWithType(isolatedIAMOwnerCTX, schemaType) request.Id = createResp.GetDetails().GetId() resp.UserSchema.Config.Type = schemaType @@ -295,12 +301,12 @@ func TestServer_GetUserSchema(t *testing.T) { } retryDuration := 5 * time.Second - if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok { + if ctxDeadline, ok := isolatedIAMOwnerCTX.Deadline(); ok { retryDuration = time.Until(ctxDeadline) } require.EventuallyWithT(t, func(ttt *assert.CollectT) { - got, err := Client.GetUserSchema(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.GetUserSchema(tt.args.ctx, tt.args.req) if tt.wantErr { assert.Error(t, err, "Error: "+err.Error()) } else { diff --git a/internal/api/grpc/resources/userschema/v3alpha/integration_test/server_test.go b/internal/api/grpc/resources/userschema/v3alpha/integration_test/server_test.go index fbdef9bee..f779a98d8 100644 --- a/internal/api/grpc/resources/userschema/v3alpha/integration_test/server_test.go +++ b/internal/api/grpc/resources/userschema/v3alpha/integration_test/server_test.go @@ -14,49 +14,41 @@ import ( "github.com/zitadel/zitadel/internal/integration" "github.com/zitadel/zitadel/pkg/grpc/feature/v2" - schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha" ) var ( - IAMOwnerCTX, SystemCTX context.Context - Instance *integration.Instance - Client schema.ZITADELUserSchemasClient + CTX context.Context ) func TestMain(m *testing.M) { os.Exit(func() int { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer cancel() - - Instance = integration.NewInstance(ctx) - - IAMOwnerCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner) - SystemCTX = integration.WithSystemAuthorization(ctx) - Client = Instance.Client.UserSchemaV3 - + CTX = ctx return m.Run() }()) } -func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) { - f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ +func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) { + ctx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) + f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(t, err) if f.UserSchema.GetEnabled() { return } - _, err = Instance.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{ + _, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{ UserSchema: gu.Ptr(true), }) require.NoError(t, err) retryDuration := time.Minute - if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok { + if ctxDeadline, ok := ctx.Deadline(); ok { retryDuration = time.Until(ctxDeadline) } require.EventuallyWithT(t, func(ttt *assert.CollectT) { - f, err := Instance.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{ + f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{ Inheritance: true, }) require.NoError(ttt, err) diff --git a/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go b/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go index f7216f842..a264b163e 100644 --- a/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go +++ b/internal/api/grpc/resources/userschema/v3alpha/integration_test/userschema_test.go @@ -19,7 +19,10 @@ import ( ) func TestServer_CreateUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) tests := []struct { name string @@ -30,7 +33,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }{ { name: "missing permission, error", - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -40,7 +43,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "empty type", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: "", @@ -50,7 +53,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "empty schema, error", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -60,7 +63,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "invalid schema, error", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -91,7 +94,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "no authenticators, ok", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -123,14 +126,14 @@ func TestServer_CreateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, }, { name: "invalid authenticator, error", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -164,7 +167,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "with authenticator, ok", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -199,14 +202,14 @@ func TestServer_CreateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, }, { name: "with invalid permission, error", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -241,7 +244,7 @@ func TestServer_CreateUserSchema(t *testing.T) { }, { name: "with valid permission, ok", - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.CreateUserSchemaRequest{ UserSchema: &schema.UserSchema{ Type: gofakeit.Name(), @@ -280,7 +283,7 @@ func TestServer_CreateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -288,7 +291,7 @@ func TestServer_CreateUserSchema(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Client.CreateUserSchema(tt.ctx, tt.req) + got, err := instance.Client.UserSchemaV3.CreateUserSchema(tt.ctx, tt.req) if tt.wantErr { require.Error(t, err) return @@ -301,7 +304,10 @@ func TestServer_CreateUserSchema(t *testing.T) { } func TestServer_UpdateUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) type args struct { ctx context.Context @@ -317,12 +323,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "missing permission, error", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: Instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), + ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner), req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ Type: gu.Ptr(gofakeit.Name()), @@ -337,7 +343,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{}, }, wantErr: true, @@ -349,7 +355,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{}, }, wantErr: true, @@ -357,12 +363,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "empty type, error", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ Type: gu.Ptr(""), @@ -374,12 +380,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "update type, ok", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ Type: gu.Ptr(gofakeit.Name()), @@ -391,7 +397,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -399,12 +405,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "empty schema, ok", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ DataType: &schema.PatchUserSchema_Schema{}, @@ -416,7 +422,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -424,12 +430,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "invalid schema, error", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ DataType: &schema.PatchUserSchema_Schema{ @@ -462,12 +468,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "update schema, ok", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ DataType: &schema.PatchUserSchema_Schema{ @@ -500,7 +506,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -508,12 +514,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "invalid authenticator, error", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ PossibleAuthenticators: []schema.AuthenticatorType{ @@ -527,12 +533,12 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "update authenticator, ok", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ PossibleAuthenticators: []schema.AuthenticatorType{ @@ -546,7 +552,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -554,8 +560,8 @@ func TestServer_UpdateUserSchema(t *testing.T) { { name: "inactive, error", prepare: func(request *schema.PatchUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() - _, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() + _, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ Id: schemaID, }) require.NoError(t, err) @@ -563,7 +569,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { return nil }, args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.PatchUserSchemaRequest{ UserSchema: &schema.PatchUserSchema{ Type: gu.Ptr(gofakeit.Name()), @@ -578,7 +584,7 @@ func TestServer_UpdateUserSchema(t *testing.T) { err := tt.prepare(tt.args.req) require.NoError(t, err) - got, err := Client.PatchUserSchema(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.PatchUserSchema(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return @@ -590,7 +596,10 @@ func TestServer_UpdateUserSchema(t *testing.T) { } func TestServer_DeactivateUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) type args struct { ctx context.Context @@ -606,7 +615,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) { { name: "not existing, error", args: args{ - IAMOwnerCTX, + isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ Id: "notexisting", }, @@ -617,10 +626,10 @@ func TestServer_DeactivateUserSchema(t *testing.T) { { name: "active, ok", args: args{ - IAMOwnerCTX, + isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{}, func(request *schema.DeactivateUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, @@ -630,7 +639,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -638,12 +647,12 @@ func TestServer_DeactivateUserSchema(t *testing.T) { { name: "inactive, error", args: args{ - IAMOwnerCTX, + isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{}, func(request *schema.DeactivateUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID - _, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ + _, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ Id: schemaID, }) return err @@ -657,7 +666,7 @@ func TestServer_DeactivateUserSchema(t *testing.T) { err := tt.args.prepare(tt.args.req) require.NoError(t, err) - got, err := Client.DeactivateUserSchema(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.DeactivateUserSchema(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return @@ -669,7 +678,10 @@ func TestServer_DeactivateUserSchema(t *testing.T) { } func TestServer_ReactivateUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) type args struct { ctx context.Context @@ -685,7 +697,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) { { name: "not existing, error", args: args{ - IAMOwnerCTX, + isolatedIAMOwnerCTX, &schema.ReactivateUserSchemaRequest{ Id: "notexisting", }, @@ -696,10 +708,10 @@ func TestServer_ReactivateUserSchema(t *testing.T) { { name: "active, error", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.ReactivateUserSchemaRequest{}, prepare: func(request *schema.ReactivateUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, @@ -709,12 +721,12 @@ func TestServer_ReactivateUserSchema(t *testing.T) { { name: "inactive, ok", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.ReactivateUserSchemaRequest{}, prepare: func(request *schema.ReactivateUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID - _, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ + _, err := instance.Client.UserSchemaV3.DeactivateUserSchema(isolatedIAMOwnerCTX, &schema.DeactivateUserSchemaRequest{ Id: schemaID, }) return err @@ -725,7 +737,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -736,7 +748,7 @@ func TestServer_ReactivateUserSchema(t *testing.T) { err := tt.args.prepare(tt.args.req) require.NoError(t, err) - got, err := Client.ReactivateUserSchema(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.ReactivateUserSchema(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return @@ -748,7 +760,10 @@ func TestServer_ReactivateUserSchema(t *testing.T) { } func TestServer_DeleteUserSchema(t *testing.T) { - ensureFeatureEnabled(t, IAMOwnerCTX) + t.Parallel() + instance := integration.NewInstance(CTX) + ensureFeatureEnabled(t, instance) + isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner) type args struct { ctx context.Context @@ -764,7 +779,7 @@ func TestServer_DeleteUserSchema(t *testing.T) { { name: "not existing, error", args: args{ - IAMOwnerCTX, + isolatedIAMOwnerCTX, &schema.DeleteUserSchemaRequest{ Id: "notexisting", }, @@ -775,10 +790,10 @@ func TestServer_DeleteUserSchema(t *testing.T) { { name: "delete, ok", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.DeleteUserSchemaRequest{}, prepare: func(request *schema.DeleteUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID return nil }, @@ -788,7 +803,7 @@ func TestServer_DeleteUserSchema(t *testing.T) { Changed: timestamppb.Now(), Owner: &object.Owner{ Type: object.OwnerType_OWNER_TYPE_INSTANCE, - Id: Instance.ID(), + Id: instance.ID(), }, }, }, @@ -796,12 +811,12 @@ func TestServer_DeleteUserSchema(t *testing.T) { { name: "deleted, error", args: args{ - ctx: IAMOwnerCTX, + ctx: isolatedIAMOwnerCTX, req: &schema.DeleteUserSchemaRequest{}, prepare: func(request *schema.DeleteUserSchemaRequest) error { - schemaID := Instance.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId() + schemaID := instance.CreateUserSchemaEmpty(isolatedIAMOwnerCTX).GetDetails().GetId() request.Id = schemaID - _, err := Client.DeleteUserSchema(IAMOwnerCTX, &schema.DeleteUserSchemaRequest{ + _, err := instance.Client.UserSchemaV3.DeleteUserSchema(isolatedIAMOwnerCTX, &schema.DeleteUserSchemaRequest{ Id: schemaID, }) return err @@ -815,7 +830,7 @@ func TestServer_DeleteUserSchema(t *testing.T) { err := tt.args.prepare(tt.args.req) require.NoError(t, err) - got, err := Client.DeleteUserSchema(tt.args.ctx, tt.args.req) + got, err := instance.Client.UserSchemaV3.DeleteUserSchema(tt.args.ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return diff --git a/internal/command/user_schema.go b/internal/command/user_schema.go index 7b53c9e16..3120071d5 100644 --- a/internal/command/user_schema.go +++ b/internal/command/user_schema.go @@ -186,7 +186,7 @@ func validateUserSchema(userSchema json.RawMessage) error { } func (c *Commands) getSchemaWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserSchemaWriteModel, error) { - writeModel := NewUserSchemaWriteModel(resourceOwner, id, "") + writeModel := NewUserSchemaWriteModel(resourceOwner, id) if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil { return nil, err } diff --git a/internal/command/user_schema_model.go b/internal/command/user_schema_model.go index e8df80479..08352e6d5 100644 --- a/internal/command/user_schema_model.go +++ b/internal/command/user_schema_model.go @@ -19,16 +19,15 @@ type UserSchemaWriteModel struct { Schema json.RawMessage PossibleAuthenticators []domain.AuthenticatorType State domain.UserSchemaState - Revision uint64 + SchemaRevision uint64 } -func NewUserSchemaWriteModel(resourceOwner, schemaID, ty string) *UserSchemaWriteModel { +func NewUserSchemaWriteModel(resourceOwner, schemaID string) *UserSchemaWriteModel { return &UserSchemaWriteModel{ WriteModel: eventstore.WriteModel{ AggregateID: schemaID, ResourceOwner: resourceOwner, }, - SchemaType: ty, } } @@ -40,13 +39,13 @@ func (wm *UserSchemaWriteModel) Reduce() error { wm.Schema = e.Schema wm.PossibleAuthenticators = e.PossibleAuthenticators wm.State = domain.UserSchemaStateActive - wm.Revision = 1 + wm.SchemaRevision = 1 case *schema.UpdatedEvent: if e.SchemaType != nil { wm.SchemaType = *e.SchemaType } if e.SchemaRevision != nil { - wm.Revision = *e.SchemaRevision + wm.SchemaRevision = *e.SchemaRevision } if len(e.Schema) > 0 { wm.Schema = e.Schema @@ -79,10 +78,6 @@ func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder { schema.DeletedType, ) - if wm.SchemaType != "" { - query = query.EventData(map[string]interface{}{"schemaType": wm.SchemaType}) - } - return query.Builder() } func (wm *UserSchemaWriteModel) NewUpdatedEvent( @@ -99,7 +94,7 @@ func (wm *UserSchemaWriteModel) NewUpdatedEvent( if !bytes.Equal(wm.Schema, userSchema) { changes = append(changes, schema.ChangeSchema(userSchema)) // change revision if the content of the schema changed - changes = append(changes, schema.IncreaseRevision(wm.Revision)) + changes = append(changes, schema.IncreaseRevision(wm.SchemaRevision)) } if len(possibleAuthenticators) > 0 && slices.Compare(wm.PossibleAuthenticators, possibleAuthenticators) != 0 { changes = append(changes, schema.ChangePossibleAuthenticators(possibleAuthenticators)) diff --git a/internal/command/user_v3.go b/internal/command/user_v3.go index f2cacd6cb..d5a097ce2 100644 --- a/internal/command/user_v3.go +++ b/internal/command/user_v3.go @@ -15,14 +15,14 @@ import ( ) type CreateSchemaUser struct { - Details *domain.ObjectDetails - ResourceOwner string + Details *domain.ObjectDetails SchemaID string schemaRevision uint64 - ID string - Data json.RawMessage + ResourceOwner string + ID string + Data json.RawMessage Email *Email ReturnCodeEmail string @@ -45,7 +45,7 @@ func (s *CreateSchemaUser) Valid(ctx context.Context, c *Commands) (err error) { if !schemaWriteModel.Exists() { return zerrors.ThrowPreconditionFailed(nil, "COMMAND-N9QOuN4F7o", "Errors.UserSchema.NotExists") } - s.schemaRevision = schemaWriteModel.Revision + s.schemaRevision = schemaWriteModel.SchemaRevision if s.ID == "" { s.ID, err = c.idGenerator.Next() @@ -120,13 +120,13 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser, ), } if user.Email != nil { - events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, events, userAgg, user.Email, alg) + events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, writeModel, events, userAgg, user.Email, alg) if err != nil { return err } } if user.Phone != nil { - events, user.ReturnCodePhone, err = c.updateSchemaUserPhone(ctx, events, userAgg, user.Phone, alg) + events, user.ReturnCodePhone, err = c.updateSchemaUserPhone(ctx, writeModel, events, userAgg, user.Phone, alg) if err != nil { return err } @@ -139,11 +139,11 @@ func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser, return nil } -func (c *Commands) DeleteSchemaUser(ctx context.Context, id string) (*domain.ObjectDetails, error) { +func (c *Commands) DeleteSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { if id == "" { return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Vs4wJCME7T", "Errors.IDMissing") } - writeModel, err := c.getSchemaUserExists(ctx, "", id) + writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id) if err != nil { return nil, err } @@ -161,7 +161,235 @@ func (c *Commands) DeleteSchemaUser(ctx context.Context, id string) (*domain.Obj return writeModelToObjectDetails(&writeModel.WriteModel), nil } -func (c *Commands) updateSchemaUserEmail(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) { +type ChangeSchemaUser struct { + Details *domain.ObjectDetails + + SchemaID *string + schemaWriteModel *UserSchemaWriteModel + + ResourceOwner string + ID string + Data json.RawMessage + + Email *Email + ReturnCodeEmail string + Phone *Phone + ReturnCodePhone string +} + +func (s *ChangeSchemaUser) Valid(ctx context.Context, c *Commands) (err error) { + if s.ID == "" { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-gEJR1QOGHb", "Errors.IDMissing") + } + if s.SchemaID != nil { + s.schemaWriteModel, err = c.getSchemaWriteModelByID(ctx, "", *s.SchemaID) + if err != nil { + return err + } + if !s.schemaWriteModel.Exists() { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists") + } + } + + if s.Email != nil && s.Email.Address != "" { + if err := s.Email.Validate(); err != nil { + return err + } + } + + if s.Phone != nil && s.Phone.Number != "" { + if s.Phone.Number, err = s.Phone.Number.Normalize(); err != nil { + return err + } + } + + return nil +} + +func (s *ChangeSchemaUser) ValidData(ctx context.Context, c *Commands, existingUser *UserV3WriteModel) (err error) { + // get role for permission check in schema through extension + role, err := c.getSchemaRoleForWrite(ctx, existingUser.ResourceOwner, existingUser.AggregateID) + if err != nil { + return err + } + + if s.schemaWriteModel == nil { + s.schemaWriteModel, err = c.getSchemaWriteModelByID(ctx, "", existingUser.SchemaID) + if err != nil { + return err + } + } + + schema, err := domain_schema.NewSchema(role, bytes.NewReader(s.schemaWriteModel.Schema)) + if err != nil { + return err + } + + // if data not changed but a new schema or revision should be used + data := s.Data + if s.Data == nil { + data = existingUser.Data + } + + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return zerrors.ThrowInvalidArgument(nil, "COMMAND-7o3ZGxtXUz", "Errors.User.Invalid") + } + + if err := schema.Validate(v); err != nil { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid") + } + return nil +} + +func (c *Commands) ChangeSchemaUser(ctx context.Context, user *ChangeSchemaUser, alg crypto.EncryptionAlgorithm) (err error) { + if err := user.Valid(ctx, c); err != nil { + return err + } + + writeModel, err := c.getSchemaUserWriteModelByID(ctx, user.ResourceOwner, user.ID) + if err != nil { + return err + } + if !writeModel.Exists() { + return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.NotFound") + } + + userAgg := UserV3AggregateFromWriteModel(&writeModel.WriteModel) + events := make([]eventstore.Command, 0) + if user.Data != nil || user.SchemaID != nil { + if err := user.ValidData(ctx, c, writeModel); err != nil { + return err + } + updateEvent := writeModel.NewUpdatedEvent(ctx, + userAgg, + user.schemaWriteModel.AggregateID, + user.schemaWriteModel.SchemaRevision, + user.Data, + ) + if updateEvent != nil { + events = append(events, updateEvent) + } + } + if user.Email != nil { + events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, writeModel, events, userAgg, user.Email, alg) + if err != nil { + return err + } + } + if user.Phone != nil { + events, user.ReturnCodePhone, err = c.updateSchemaUserPhone(ctx, writeModel, events, userAgg, user.Phone, alg) + if err != nil { + return err + } + } + if len(events) == 0 { + user.Details = writeModelToObjectDetails(&writeModel.WriteModel) + return nil + } + if err := c.pushAppendAndReduce(ctx, writeModel, events...); err != nil { + return err + } + user.Details = writeModelToObjectDetails(&writeModel.WriteModel) + return nil +} + +func (c *Commands) checkPermissionUpdateUserState(ctx context.Context, resourceOwner, userID string) error { + return c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID) +} + +func (c *Commands) LockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Eu8I2VAfjF", "Errors.IDMissing") + } + writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id) + if err != nil { + return nil, err + } + if !writeModel.Exists() || writeModel.Locked { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound") + } + if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil { + return nil, err + } + if err := c.pushAppendAndReduce(ctx, writeModel, + schemauser.NewLockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)), + ); err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.WriteModel), nil +} + +func (c *Commands) UnlockSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-krXtYscQZh", "Errors.IDMissing") + } + writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id) + if err != nil { + return nil, err + } + if !writeModel.Exists() || !writeModel.Locked { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound") + } + if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil { + return nil, err + } + if err := c.pushAppendAndReduce(ctx, writeModel, + schemauser.NewUnlockedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)), + ); err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.WriteModel), nil +} + +func (c *Commands) DeactivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-pjJhge86ZV", "Errors.IDMissing") + } + writeModel, err := c.getSchemaUserExists(ctx, resourceOwner, id) + if err != nil { + return nil, err + } + if writeModel.State != domain.UserStateActive { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound") + } + if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil { + return nil, err + } + if err := c.pushAppendAndReduce(ctx, writeModel, + schemauser.NewDeactivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)), + ); err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.WriteModel), nil +} + +func (c *Commands) ActivateSchemaUser(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) { + if id == "" { + return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-17XupGvxBJ", "Errors.IDMissing") + } + writeModel, err := c.getSchemaUserExists(ctx, "", id) + if err != nil { + return nil, err + } + if writeModel.State != domain.UserStateInactive { + return nil, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound") + } + if err := c.checkPermissionUpdateUserState(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil { + return nil, err + } + if err := c.pushAppendAndReduce(ctx, writeModel, + schemauser.NewActivatedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)), + ); err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.WriteModel), nil +} + +func (c *Commands) updateSchemaUserEmail(ctx context.Context, existing *UserV3WriteModel, events []eventstore.Command, agg *eventstore.Aggregate, email *Email, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) { + if existing.Email == string(email.Address) { + return events, plainCode, nil + } events = append(events, schemauser.NewEmailUpdatedEvent(ctx, agg, @@ -187,8 +415,12 @@ func (c *Commands) updateSchemaUserEmail(ctx context.Context, events []eventstor return events, plainCode, nil } -func (c *Commands) updateSchemaUserPhone(ctx context.Context, events []eventstore.Command, agg *eventstore.Aggregate, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) { - events = append(events, schemauser.NewPhoneChangedEvent(ctx, +func (c *Commands) updateSchemaUserPhone(ctx context.Context, existing *UserV3WriteModel, events []eventstore.Command, agg *eventstore.Aggregate, phone *Phone, alg crypto.EncryptionAlgorithm) (_ []eventstore.Command, plainCode string, err error) { + if existing.Phone == string(phone.Number) { + return events, plainCode, nil + } + + events = append(events, schemauser.NewPhoneUpdatedEvent(ctx, agg, phone.Number, )) @@ -218,3 +450,11 @@ func (c *Commands) getSchemaUserExists(ctx context.Context, resourceOwner, id st } return writeModel, nil } + +func (c *Commands) getSchemaUserWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) { + writeModel := NewUserV3WriteModel(resourceOwner, id) + if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil { + return nil, err + } + return writeModel, nil +} diff --git a/internal/command/user_v3_model.go b/internal/command/user_v3_model.go index 51f783aae..623155817 100644 --- a/internal/command/user_v3_model.go +++ b/internal/command/user_v3_model.go @@ -29,7 +29,8 @@ type UserV3WriteModel struct { Data json.RawMessage - State domain.UserState + Locked bool + State domain.UserState } func NewExistsUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel { @@ -61,8 +62,9 @@ func (wm *UserV3WriteModel) Reduce() error { switch e := event.(type) { case *schemauser.CreatedEvent: wm.SchemaID = e.SchemaID - wm.SchemaRevision = 1 + wm.SchemaRevision = e.SchemaRevision wm.Data = e.Data + wm.Locked = false wm.State = domain.UserStateActive case *schemauser.UpdatedEvent: @@ -79,6 +81,8 @@ func (wm *UserV3WriteModel) Reduce() error { wm.State = domain.UserStateDeleted case *schemauser.EmailUpdatedEvent: wm.Email = string(e.EmailAddress) + wm.IsEmailVerified = false + wm.EmailVerifiedFailedCount = 0 case *schemauser.EmailCodeAddedEvent: wm.IsEmailVerified = false wm.EmailVerifiedFailedCount = 0 @@ -87,8 +91,10 @@ func (wm *UserV3WriteModel) Reduce() error { wm.EmailVerifiedFailedCount = 0 case *schemauser.EmailVerificationFailedEvent: wm.EmailVerifiedFailedCount += 1 - case *schemauser.PhoneChangedEvent: + case *schemauser.PhoneUpdatedEvent: wm.Phone = string(e.PhoneNumber) + wm.IsPhoneVerified = false + wm.PhoneVerifiedFailedCount = 0 case *schemauser.PhoneCodeAddedEvent: wm.IsPhoneVerified = false wm.PhoneVerifiedFailedCount = 0 @@ -97,28 +103,39 @@ func (wm *UserV3WriteModel) Reduce() error { wm.IsPhoneVerified = true case *schemauser.PhoneVerificationFailedEvent: wm.PhoneVerifiedFailedCount += 1 + case *schemauser.LockedEvent: + wm.Locked = true + case *schemauser.UnlockedEvent: + wm.Locked = false + case *schemauser.DeactivatedEvent: + wm.State = domain.UserStateInactive + case *schemauser.ActivatedEvent: + wm.State = domain.UserStateActive } } return wm.WriteModel.Reduce() } func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder { - query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). - ResourceOwner(wm.ResourceOwner). - AddQuery(). - AggregateTypes(schemauser.AggregateType). - AggregateIDs(wm.AggregateID). - EventTypes( - schemauser.CreatedType, - schemauser.DeletedType, - ) + builder := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent) + if wm.ResourceOwner != "" { + builder = builder.ResourceOwner(wm.ResourceOwner) + } + eventtypes := []eventstore.EventType{ + schemauser.CreatedType, + schemauser.DeletedType, + schemauser.ActivatedType, + schemauser.DeactivatedType, + schemauser.LockedType, + schemauser.UnlockedType, + } if wm.DataWM { - query = query.EventTypes( + eventtypes = append(eventtypes, schemauser.UpdatedType, ) } if wm.EmailWM { - query = query.EventTypes( + eventtypes = append(eventtypes, schemauser.EmailUpdatedType, schemauser.EmailVerifiedType, schemauser.EmailCodeAddedType, @@ -126,31 +143,34 @@ func (wm *UserV3WriteModel) Query() *eventstore.SearchQueryBuilder { ) } if wm.PhoneWM { - query = query.EventTypes( + eventtypes = append(eventtypes, schemauser.PhoneUpdatedType, schemauser.PhoneVerifiedType, schemauser.PhoneCodeAddedType, schemauser.PhoneVerificationFailedType, ) } - return query.Builder() + return builder.AddQuery(). + AggregateTypes(schemauser.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes(eventtypes...).Builder() } func (wm *UserV3WriteModel) NewUpdatedEvent( ctx context.Context, agg *eventstore.Aggregate, - schemaID *string, - schemaRevision *uint64, + schemaID string, + schemaRevision uint64, data json.RawMessage, ) *schemauser.UpdatedEvent { changes := make([]schemauser.Changes, 0) - if schemaID != nil && wm.SchemaID != *schemaID { - changes = append(changes, schemauser.ChangeSchemaID(wm.SchemaID, *schemaID)) + if wm.SchemaID != schemaID { + changes = append(changes, schemauser.ChangeSchemaID(schemaID)) } - if schemaRevision != nil && wm.SchemaRevision != *schemaRevision { - changes = append(changes, schemauser.ChangeSchemaRevision(wm.SchemaRevision, *schemaRevision)) + if wm.SchemaRevision != schemaRevision { + changes = append(changes, schemauser.ChangeSchemaRevision(schemaRevision)) } - if !bytes.Equal(wm.Data, data) { + if data != nil && !bytes.Equal(wm.Data, data) { changes = append(changes, schemauser.ChangeData(data)) } if len(changes) == 0 { diff --git a/internal/command/user_v3_test.go b/internal/command/user_v3_test.go index 5bbb9e0c5..69794b3c2 100644 --- a/internal/command/user_v3_test.go +++ b/internal/command/user_v3_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/muhlemmer/gu" "github.com/stretchr/testify/assert" "go.uber.org/mock/gomock" @@ -673,7 +674,7 @@ func TestCommands_CreateSchemaUser(t *testing.T) { "name": "user" }`), ), - schemauser.NewPhoneChangedEvent(context.Background(), + schemauser.NewPhoneUpdatedEvent(context.Background(), &schemauser.NewAggregate("id1", "org1").Aggregate, "+41791234567", ), @@ -751,7 +752,7 @@ func TestCommands_CreateSchemaUser(t *testing.T) { "name": "user" }`), ), - schemauser.NewPhoneChangedEvent(context.Background(), + schemauser.NewPhoneUpdatedEvent(context.Background(), &schemauser.NewAggregate("id1", "org1").Aggregate, "+41791234567", ), @@ -834,7 +835,7 @@ func TestCommands_CreateSchemaUser(t *testing.T) { schemauser.NewEmailVerifiedEvent(context.Background(), &schemauser.NewAggregate("id1", "org1").Aggregate, ), - schemauser.NewPhoneChangedEvent(context.Background(), + schemauser.NewPhoneUpdatedEvent(context.Background(), &schemauser.NewAggregate("id1", "org1").Aggregate, "+41791234567", ), @@ -904,6 +905,7 @@ func TestCommandSide_DeleteSchemaUser(t *testing.T) { type ( args struct { ctx context.Context + orgID string userID string } ) @@ -1088,7 +1090,7 @@ func TestCommandSide_DeleteSchemaUser(t *testing.T) { eventstore: tt.fields.eventstore(t), checkPermission: tt.fields.checkPermission, } - got, err := r.DeleteSchemaUser(tt.args.ctx, tt.args.userID) + got, err := r.DeleteSchemaUser(tt.args.ctx, tt.args.orgID, tt.args.userID) if tt.res.err == nil { assert.NoError(t, err) } @@ -1101,3 +1103,2022 @@ func TestCommandSide_DeleteSchemaUser(t *testing.T) { }) } } + +func TestCommandSide_LockSchemaUser(t *testing.T) { + type fields struct { + eventstore func(*testing.T) *eventstore.Eventstore + checkPermission domain.PermissionCheck + } + type ( + args struct { + ctx context.Context + orgID string + userID string + } + ) + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "userid missing, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-Eu8I2VAfjF", "Errors.IDMissing")) + }, + }, + }, + { + name: "user not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user removed, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeletedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user locked, precondition error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewLockedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-G4LOrnjY7q", "Errors.User.NotFound")) + }, + }, + }, + { + name: "lock user, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + expectPush( + schemauser.NewLockedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "lock user, no permission", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), + checkPermission: tt.fields.checkPermission, + } + got, err := r.LockSchemaUser(tt.args.ctx, tt.args.orgID, tt.args.userID) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assertObjectDetails(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_UnlockSchemaUser(t *testing.T) { + type fields struct { + eventstore func(*testing.T) *eventstore.Eventstore + checkPermission domain.PermissionCheck + } + type ( + args struct { + ctx context.Context + orgID string + userID string + } + ) + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "userid missing, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-krXtYscQZh", "Errors.IDMissing")) + }, + }, + }, + { + name: "user not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user removed, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeletedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user not locked, precondition error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-gpBv46Lh9m", "Errors.User.NotFound")) + }, + }, + }, + { + name: "unlock user, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewLockedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + expectPush( + schemauser.NewUnlockedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "unlock user, no permission", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewLockedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), + checkPermission: tt.fields.checkPermission, + } + got, err := r.UnlockSchemaUser(tt.args.ctx, tt.args.orgID, tt.args.userID) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assertObjectDetails(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_DeactivateSchemaUser(t *testing.T) { + type fields struct { + eventstore func(*testing.T) *eventstore.Eventstore + checkPermission domain.PermissionCheck + } + type ( + args struct { + ctx context.Context + orgID string + userID string + } + ) + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "userid missing, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-pjJhge86ZV", "Errors.IDMissing")) + }, + }, + }, + { + name: "user not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user removed, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeletedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user not active, precondition error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeactivatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-Ob6lR5iFTe", "Errors.User.NotFound")) + }, + }, + }, + { + name: "deactivate user, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + expectPush( + schemauser.NewDeactivatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "deactivate user, no permission", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), + checkPermission: tt.fields.checkPermission, + } + got, err := r.DeactivateSchemaUser(tt.args.ctx, tt.args.orgID, tt.args.userID) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assertObjectDetails(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_ReactivateSchemaUser(t *testing.T) { + type fields struct { + eventstore func(*testing.T) *eventstore.Eventstore + checkPermission domain.PermissionCheck + } + type ( + args struct { + ctx context.Context + orgID string + userID string + } + ) + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "userid missing, invalid argument error", + fields: fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-17XupGvxBJ", "Errors.IDMissing")) + }, + }, + }, + { + name: "user not existing, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user removed, not found error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeletedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")) + }, + }, + }, + { + name: "user not inactive, precondition error", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowNotFound(nil, "COMMAND-rQjbBr4J3j", "Errors.User.NotFound")) + }, + }, + }, + { + name: "activate user, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeactivatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + expectPush( + schemauser.NewActivatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "activate user, no permission", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "schema", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewDeactivatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args: args{ + ctx: context.Background(), + userID: "user1", + }, + res: res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore(t), + checkPermission: tt.fields.checkPermission, + } + got, err := r.ActivateSchemaUser(tt.args.ctx, tt.args.orgID, tt.args.userID) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assertObjectDetails(t, tt.res.want, got) + } + }) + } +} + +func TestCommands_ChangeSchemaUser(t *testing.T) { + type fields struct { + eventstore func(t *testing.T) *eventstore.Eventstore + checkPermission domain.PermissionCheck + newCode encrypedCodeFunc + } + type args struct { + ctx context.Context + user *ChangeSchemaUser + } + type res struct { + returnCodeEmail string + returnCodePhone string + details *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "no userID, error", + fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "COMMAND-gEJR1QOGHb", "Errors.IDMissing")) + }, + }, + }, + { + "schema not existing, error", + fields{ + eventstore: expectEventstore( + expectFilter(), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("type"), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-VLDTtxT3If", "Errors.UserSchema.NotExists")) + }, + }, + }, + { + "no valid email, error", + fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Email: &Email{Address: "noemail"}, + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "EMAIL-599BI", "Errors.User.Email.Invalid")) + }, + }, + }, + { + "no valid phone, error", + fields{ + eventstore: expectEventstore(), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Phone: &Phone{Number: "invalid"}, + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "PHONE-so0wa", "Errors.User.Phone.Invalid")) + }, + }, + }, + { + "user update, no permission", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Data: json.RawMessage(`{ + "name": "user" + }`), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) + }, + }, + }, + { + "user updated, same schema", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + 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" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeData( + json.RawMessage(`{ + "name": "user2" + }`), + ), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Data: json.RawMessage(`{ + "name": "user2" + }`), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, changed schema", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schema.NewCreatedEvent( + context.Background(), + &schema.NewAggregate("id2", "instanceID").Aggregate, + "type", + json.RawMessage(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeSchemaID("id2"), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id2"), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, new schema", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schema.NewCreatedEvent( + context.Background(), + &schema.NewAggregate("id2", "instanceID").Aggregate, + "type", + json.RawMessage(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeSchemaID("id2"), + schemauser.ChangeData( + json.RawMessage(`{ + "name": "user2" + }`), + ), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id2"), + Data: json.RawMessage(`{ + "name": "user2" + }`), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, same schema revision", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name1": "user1" + }`), + ), + ), + ), + expectFilter( + eventFromEventPusher( + schema.NewCreatedEvent( + context.Background(), + &schema.NewAggregate("id1", "instanceID").Aggregate, + "type", + json.RawMessage(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name1": { + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + eventFromEventPusher( + schema.NewUpdatedEvent( + context.Background(), + &schema.NewAggregate("id1", "instanceID").Aggregate, + []schema.Changes{ + schema.IncreaseRevision(1), + schema.ChangeSchema(json.RawMessage(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name2": { + "type": "string" + } + } + }`)), + }, + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeSchemaRevision(2), + schemauser.ChangeData( + json.RawMessage(`{ + "name2": "user2" + }`), + ), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Data: json.RawMessage(`{ + "name2": "user2" + }`), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, new schema and revision", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schema.NewCreatedEvent( + context.Background(), + &schema.NewAggregate("id2", "instanceID").Aggregate, + "type", + json.RawMessage(`{ + "$schema": "urn:zitadel:schema:v1", + "type": "object", + "properties": { + "name2": { + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 2, + json.RawMessage(`{ + "name1": "user1" + }`), + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeSchemaID("id2"), + schemauser.ChangeSchemaRevision(1), + schemauser.ChangeData( + json.RawMessage(`{ + "name2": "user2" + }`), + ), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id2"), + Data: json.RawMessage(`{ + "name2": "user2" + }`), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user update, no field permission as admin", + 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": { + "urn:zitadel:schema:permission": { + "owner": "r" + }, + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id1"), + Data: json.RawMessage(`{ + "name": "user" + }`), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")) + }, + }, + }, + { + "user update, no field permission as user", + 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": { + "urn:zitadel:schema:permission": { + "self": "r" + }, + "type": "string" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + ), + }, + args{ + ctx: authz.NewMockContext("instanceID", "org1", "user1"), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("type"), + Data: json.RawMessage(`{ + "name": "user" + }`), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")) + }, + }, + }, + { + "user update, invalid data type", + 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" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "org1", "user1"), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("type"), + Data: json.RawMessage(`{ + "name": 1 + }`), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")) + }, + }, + }, + { + "user update, additional property", + 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" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + expectPush( + schemauser.NewUpdatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + []schemauser.Changes{ + schemauser.ChangeData( + json.RawMessage(`{ + "name": "user1", + "additional": "property" + }`), + ), + }, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id1"), + Data: json.RawMessage(`{ + "name": "user1", + "additional": "property" + }`), + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user update, invalid data attribute name", + 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" + } + }, + "additionalProperties": false + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "org1", "user1"), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("type"), + Data: json.RawMessage(`{ + "invalid": "user" + }`), + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, zerrors.ThrowPreconditionFailed(nil, "COMMAND-SlKXqLSeL6", "Errors.UserSchema.Data.Invalid")) + }, + }, + }, + { + "user update, email not changed", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewEmailUpdatedEvent( + context.Background(), + &schema.NewAggregate("id1", "instanceID").Aggregate, + "test@example.com", + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Email: &Email{ + Address: "test@example.com", + ReturnCode: true, + URLTemplate: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user update, email return", + 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" + } + } + }`), + []domain.AuthenticatorType{domain.AuthenticatorTypeUsername}, + ), + ), + ), + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "id1", + 1, + json.RawMessage(`{ + "name": "user1" + }`), + ), + ), + ), + expectPush( + schemauser.NewEmailUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "test@example.com", + ), + schemauser.NewEmailCodeAddedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("emailverify"), + }, + time.Hour*1, + "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", + true, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + newCode: mockEncryptedCode("emailverify", time.Hour), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + SchemaID: gu.Ptr("id1"), + Email: &Email{ + Address: "test@example.com", + ReturnCode: true, + URLTemplate: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + returnCodeEmail: "emailverify", + }, + }, + { + "user updated, email to verify", + fields{ + eventstore: expectEventstore( + expectFilter( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + )), + expectPush( + schemauser.NewEmailUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "test@example.com", + ), + schemauser.NewEmailCodeAddedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("emailverify"), + }, + time.Hour*1, + "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", + false, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + newCode: mockEncryptedCode("emailverify", time.Hour), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Email: &Email{ + Address: "test@example.com", + URLTemplate: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}", + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, phone no change", + fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + eventFromEventPusher( + schemauser.NewPhoneUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "+41791234567", + ), + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Phone: &Phone{ + Number: "+41791234567", + ReturnCode: true, + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, phone return", + fields{ + eventstore: expectEventstore( + expectFilter( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + expectPush( + schemauser.NewPhoneUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "+41791234567", + ), + schemauser.NewPhoneCodeAddedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("phoneverify"), + }, + time.Hour*1, + true, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + newCode: mockEncryptedCode("phoneverify", time.Hour), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Phone: &Phone{ + Number: "+41791234567", + ReturnCode: true, + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + returnCodePhone: "phoneverify", + }, + }, + { + "user updated, phone to verify", + fields{ + eventstore: expectEventstore( + expectFilter( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("id1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + expectPush( + schemauser.NewPhoneUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "+41791234567", + ), + schemauser.NewPhoneCodeAddedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("phoneverify"), + }, + time.Hour*1, + false, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + newCode: mockEncryptedCode("phoneverify", time.Hour), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Phone: &Phone{ + Number: "+41791234567", + }, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + "user updated, full verified", + fields{ + eventstore: expectEventstore( + expectFilter( + schemauser.NewCreatedEvent( + context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "type", + 1, + json.RawMessage(`{ + "name": "user" + }`), + ), + ), + expectPush( + schemauser.NewEmailUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "test@example.com", + ), + schemauser.NewEmailVerifiedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + schemauser.NewPhoneUpdatedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + "+41791234567", + ), + schemauser.NewPhoneVerifiedEvent(context.Background(), + &schemauser.NewAggregate("user1", "org1").Aggregate, + ), + ), + ), + checkPermission: newMockPermissionCheckAllowed(), + }, + args{ + ctx: authz.NewMockContext("instanceID", "", ""), + user: &ChangeSchemaUser{ + ID: "user1", + Email: &Email{Address: "test@example.com", Verified: true}, + Phone: &Phone{Number: "+41791234567", Verified: true}, + }, + }, + res{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore(t), + checkPermission: tt.fields.checkPermission, + newEncryptedCode: tt.fields.newCode, + } + err := c.ChangeSchemaUser(tt.args.ctx, tt.args.user, crypto.CreateMockEncryptionAlg(gomock.NewController(t))) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assertObjectDetails(t, tt.res.details, tt.args.user.Details) + } + + if tt.res.returnCodePhone != "" { + assert.Equal(t, tt.res.returnCodePhone, tt.args.user.ReturnCodePhone) + } + if tt.res.returnCodeEmail != "" { + assert.Equal(t, tt.res.returnCodeEmail, tt.args.user.ReturnCodeEmail) + } + }) + } +} diff --git a/internal/integration/client.go b/internal/integration/client.go index 82b8ab3b6..9bf855f5c 100644 --- a/internal/integration/client.go +++ b/internal/integration/client.go @@ -28,7 +28,7 @@ import ( object_v3alpha "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha" oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2" oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta" - org "github.com/zitadel/zitadel/pkg/grpc/org/v2" + "github.com/zitadel/zitadel/pkg/grpc/org/v2" org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta" action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha" user_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha" @@ -784,3 +784,55 @@ func (i *Instance) CreateInviteCode(ctx context.Context, userID string) *user_v2 logging.OnError(err).Fatal("create invite code") return user } + +func (i *Instance) LockSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.LockUserResponse { + var org *object_v3alpha.Organization + if orgID != "" { + org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}} + } + user, err := i.Client.UserV3Alpha.LockUser(ctx, &user_v3alpha.LockUserRequest{ + Organization: org, + Id: userID, + }) + logging.OnError(err).Fatal("lock user") + return user +} + +func (i *Instance) UnlockSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.UnlockUserResponse { + var org *object_v3alpha.Organization + if orgID != "" { + org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}} + } + user, err := i.Client.UserV3Alpha.UnlockUser(ctx, &user_v3alpha.UnlockUserRequest{ + Organization: org, + Id: userID, + }) + logging.OnError(err).Fatal("unlock user") + return user +} + +func (i *Instance) DeactivateSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.DeactivateUserResponse { + var org *object_v3alpha.Organization + if orgID != "" { + org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}} + } + user, err := i.Client.UserV3Alpha.DeactivateUser(ctx, &user_v3alpha.DeactivateUserRequest{ + Organization: org, + Id: userID, + }) + logging.OnError(err).Fatal("deactivate user") + return user +} + +func (i *Instance) ActivateSchemaUser(ctx context.Context, orgID string, userID string) *user_v3alpha.ActivateUserResponse { + var org *object_v3alpha.Organization + if orgID != "" { + org = &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}} + } + user, err := i.Client.UserV3Alpha.ActivateUser(ctx, &user_v3alpha.ActivateUserRequest{ + Organization: org, + Id: userID, + }) + logging.OnError(err).Fatal("reactivate user") + return user +} diff --git a/internal/repository/user/schemauser/aggregate.go b/internal/repository/user/schemauser/aggregate.go index 1c9901c08..18b901281 100644 --- a/internal/repository/user/schemauser/aggregate.go +++ b/internal/repository/user/schemauser/aggregate.go @@ -5,8 +5,8 @@ import ( ) const ( - AggregateType = "user" - AggregateVersion = "v3" + AggregateType = "schemauser" + AggregateVersion = "v1" ) type Aggregate struct { diff --git a/internal/repository/user/schemauser/email.go b/internal/repository/user/schemauser/email.go index 07ae1bdf7..b5e420625 100644 --- a/internal/repository/user/schemauser/email.go +++ b/internal/repository/user/schemauser/email.go @@ -8,7 +8,6 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -21,11 +20,15 @@ const ( ) type EmailUpdatedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` EmailAddress domain.EmailAddress `json:"email,omitempty"` } +func (e *EmailUpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *EmailUpdatedEvent) Payload() interface{} { return e } @@ -36,7 +39,7 @@ func (e *EmailUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress domain.EmailAddress) *EmailUpdatedEvent { return &EmailUpdatedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, EmailUpdatedType, @@ -45,24 +48,16 @@ func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, } } -func EmailUpdatedEventMapper(event eventstore.Event) (eventstore.Event, error) { - emailChangedEvent := &EmailUpdatedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(emailChangedEvent) - if err != nil { - return nil, zerrors.ThrowInternal(err, "USER-4M0sd", "unable to unmarshal human password changed") - } - - return emailChangedEvent, nil -} - type EmailVerifiedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` IsEmailVerified bool `json:"-"` } +func (e *EmailVerifiedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *EmailVerifiedEvent) Payload() interface{} { return nil } @@ -73,7 +68,7 @@ func (e *EmailVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerifiedEvent { return &EmailVerifiedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, EmailVerifiedType, @@ -81,18 +76,13 @@ func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) } } -func HumanVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) { - emailVerified := &EmailVerifiedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - IsEmailVerified: true, - } - return emailVerified, nil -} - type EmailVerificationFailedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` } +func (e *EmailVerificationFailedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} func (e *EmailVerificationFailedEvent) Payload() interface{} { return nil } @@ -101,9 +91,9 @@ func (e *EmailVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueC return nil } -func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent { +func NewEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent { return &EmailVerificationFailedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, EmailVerificationFailedType, @@ -111,14 +101,8 @@ func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *events } } -func EmailVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) { - return &EmailVerificationFailedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil -} - type EmailCodeAddedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` Code *crypto.CryptoValue `json:"code,omitempty"` Expiry time.Duration `json:"expiry,omitempty"` @@ -127,6 +111,10 @@ type EmailCodeAddedEvent struct { TriggeredAtOrigin string `json:"triggerOrigin,omitempty"` } +func (e *EmailCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func (e *EmailCodeAddedEvent) Payload() interface{} { return e } @@ -148,7 +136,7 @@ func NewEmailCodeAddedEvent( codeReturned bool, ) *EmailCodeAddedEvent { return &EmailCodeAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, EmailCodeAddedType, @@ -161,22 +149,13 @@ func NewEmailCodeAddedEvent( } } -func EmailCodeAddedEventMapper(event eventstore.Event) (eventstore.Event, error) { - codeAdded := &EmailCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(codeAdded) - if err != nil { - return nil, zerrors.ThrowInternal(err, "USER-3M0sd", "unable to unmarshal human email code added") - } - - return codeAdded, nil -} - type EmailCodeSentEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` } +func (e *EmailCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} func (e *EmailCodeSentEvent) Payload() interface{} { return nil } @@ -185,18 +164,12 @@ func (e *EmailCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint return nil } -func NewHumanEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent { +func NewEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent { return &EmailCodeSentEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, EmailCodeSentType, ), } } - -func EmailCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) { - return &EmailCodeSentEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil -} diff --git a/internal/repository/user/schemauser/eventstore.go b/internal/repository/user/schemauser/eventstore.go index b9cf03e5d..85ad15c17 100644 --- a/internal/repository/user/schemauser/eventstore.go +++ b/internal/repository/user/schemauser/eventstore.go @@ -6,4 +6,18 @@ func init() { eventstore.RegisterFilterEventMapper(AggregateType, CreatedType, eventstore.GenericEventMapper[CreatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, UpdatedType, eventstore.GenericEventMapper[UpdatedEvent]) eventstore.RegisterFilterEventMapper(AggregateType, DeletedType, eventstore.GenericEventMapper[DeletedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, LockedType, eventstore.GenericEventMapper[LockedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, UnlockedType, eventstore.GenericEventMapper[UnlockedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, ActivatedType, eventstore.GenericEventMapper[ActivatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, DeactivatedType, eventstore.GenericEventMapper[DeactivatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, EmailUpdatedType, eventstore.GenericEventMapper[EmailUpdatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, EmailCodeAddedType, eventstore.GenericEventMapper[EmailCodeAddedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, EmailCodeSentType, eventstore.GenericEventMapper[EmailCodeSentEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, EmailVerifiedType, eventstore.GenericEventMapper[EmailVerifiedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, EmailVerificationFailedType, eventstore.GenericEventMapper[EmailVerificationFailedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, PhoneUpdatedType, eventstore.GenericEventMapper[PhoneUpdatedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, PhoneCodeAddedType, eventstore.GenericEventMapper[PhoneCodeAddedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, PhoneCodeSentType, eventstore.GenericEventMapper[PhoneCodeSentEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, PhoneVerifiedType, eventstore.GenericEventMapper[PhoneVerifiedEvent]) + eventstore.RegisterFilterEventMapper(AggregateType, PhoneVerificationFailedType, eventstore.GenericEventMapper[PhoneVerificationFailedEvent]) } diff --git a/internal/repository/user/schemauser/phone.go b/internal/repository/user/schemauser/phone.go index 5110772c0..a491dab77 100644 --- a/internal/repository/user/schemauser/phone.go +++ b/internal/repository/user/schemauser/phone.go @@ -8,7 +8,6 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" - "github.com/zitadel/zitadel/internal/zerrors" ) const ( @@ -20,23 +19,27 @@ const ( PhoneCodeSentType = phoneEventPrefix + "code.sent" ) -type PhoneChangedEvent struct { - eventstore.BaseEvent `json:"-"` +type PhoneUpdatedEvent struct { + *eventstore.BaseEvent `json:"-"` PhoneNumber domain.PhoneNumber `json:"phone,omitempty"` } -func (e *PhoneChangedEvent) Payload() interface{} { +func (e *PhoneUpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *PhoneUpdatedEvent) Payload() interface{} { return e } -func (e *PhoneChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { +func (e *PhoneUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { return nil } -func NewPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *PhoneChangedEvent { - return &PhoneChangedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( +func NewPhoneUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *PhoneUpdatedEvent { + return &PhoneUpdatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, PhoneUpdatedType, @@ -45,24 +48,15 @@ func NewPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, } } -func PhoneChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { - phoneChangedEvent := &PhoneChangedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(phoneChangedEvent) - if err != nil { - return nil, zerrors.ThrowInternal(err, "USER-5M0pd", "unable to unmarshal phone changed") - } - - return phoneChangedEvent, nil -} - type PhoneVerifiedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` IsPhoneVerified bool `json:"-"` } +func (e *PhoneVerifiedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} func (e *PhoneVerifiedEvent) Payload() interface{} { return nil } @@ -73,7 +67,7 @@ func (e *PhoneVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint func NewPhoneVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerifiedEvent { return &PhoneVerifiedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, PhoneVerifiedType, @@ -81,15 +75,12 @@ func NewPhoneVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) } } -func PhoneVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) { - return &PhoneVerifiedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - IsPhoneVerified: true, - }, nil +type PhoneVerificationFailedEvent struct { + *eventstore.BaseEvent `json:"-"` } -type PhoneVerificationFailedEvent struct { - eventstore.BaseEvent `json:"-"` +func (e *PhoneVerificationFailedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event } func (e *PhoneVerificationFailedEvent) Payload() interface{} { @@ -102,7 +93,7 @@ func (e *PhoneVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueC func NewPhoneVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerificationFailedEvent { return &PhoneVerificationFailedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, PhoneVerificationFailedType, @@ -110,14 +101,8 @@ func NewPhoneVerificationFailedEvent(ctx context.Context, aggregate *eventstore. } } -func PhoneVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) { - return &PhoneVerificationFailedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil -} - type PhoneCodeAddedEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` Code *crypto.CryptoValue `json:"code,omitempty"` Expiry time.Duration `json:"expiry,omitempty"` @@ -137,6 +122,10 @@ func (e *PhoneCodeAddedEvent) TriggerOrigin() string { return e.TriggeredAtOrigin } +func (e *PhoneCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func NewPhoneCodeAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, @@ -145,7 +134,7 @@ func NewPhoneCodeAddedEvent( codeReturned bool, ) *PhoneCodeAddedEvent { return &PhoneCodeAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, PhoneCodeAddedType, @@ -157,20 +146,8 @@ func NewPhoneCodeAddedEvent( } } -func PhoneCodeAddedEventMapper(event eventstore.Event) (eventstore.Event, error) { - codeAdded := &PhoneCodeAddedEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - } - err := event.Unmarshal(codeAdded) - if err != nil { - return nil, zerrors.ThrowInternal(err, "USER-6Ms9d", "unable to unmarshal phone code added") - } - - return codeAdded, nil -} - type PhoneCodeSentEvent struct { - eventstore.BaseEvent `json:"-"` + *eventstore.BaseEvent `json:"-"` } func (e *PhoneCodeSentEvent) Payload() interface{} { @@ -181,18 +158,16 @@ func (e *PhoneCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint return nil } +func (e *PhoneCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + func NewPhoneCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneCodeSentEvent { return &PhoneCodeSentEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: eventstore.NewBaseEventForPush( ctx, aggregate, PhoneCodeSentType, ), } } - -func PhoneCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) { - return &PhoneCodeSentEvent{ - BaseEvent: *eventstore.BaseEventFromRepo(event), - }, nil -} diff --git a/internal/repository/user/schemauser/user.go b/internal/repository/user/schemauser/user.go index 4c88d5308..6583e5736 100644 --- a/internal/repository/user/schemauser/user.go +++ b/internal/repository/user/schemauser/user.go @@ -8,10 +8,14 @@ import ( ) const ( - eventPrefix = "user." - CreatedType = eventPrefix + "created" - UpdatedType = eventPrefix + "updated" - DeletedType = eventPrefix + "deleted" + eventPrefix = "schemauser." + CreatedType = eventPrefix + "created" + UpdatedType = eventPrefix + "updated" + DeletedType = eventPrefix + "deleted" + LockedType = eventPrefix + "locked" + UnlockedType = eventPrefix + "unlocked" + DeactivatedType = eventPrefix + "deactivated" + ActivatedType = eventPrefix + "activated" ) type CreatedEvent struct { @@ -60,8 +64,6 @@ type UpdatedEvent struct { SchemaID *string `json:"schemaID,omitempty"` SchemaRevision *uint64 `json:"schemaRevision,omitempty"` Data json.RawMessage `json:"schema,omitempty"` - oldSchemaID string - oldRevision uint64 } func (e *UpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { @@ -95,16 +97,14 @@ func NewUpdatedEvent( type Changes func(event *UpdatedEvent) -func ChangeSchemaID(oldSchemaID, schemaID string) func(event *UpdatedEvent) { +func ChangeSchemaID(schemaID string) func(event *UpdatedEvent) { return func(e *UpdatedEvent) { e.SchemaID = &schemaID - e.oldSchemaID = oldSchemaID } } -func ChangeSchemaRevision(oldSchemaRevision, schemaRevision uint64) func(event *UpdatedEvent) { +func ChangeSchemaRevision(schemaRevision uint64) func(event *UpdatedEvent) { return func(e *UpdatedEvent) { e.SchemaRevision = &schemaRevision - e.oldRevision = oldSchemaRevision } } @@ -142,3 +142,119 @@ func NewDeletedEvent( ), } } + +type LockedEvent struct { + *eventstore.BaseEvent `json:"-"` +} + +func (e *LockedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *LockedEvent) Payload() interface{} { + return e +} + +func (e *LockedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func NewLockedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *LockedEvent { + return &LockedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + LockedType, + ), + } +} + +type UnlockedEvent struct { + *eventstore.BaseEvent `json:"-"` +} + +func (e *UnlockedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *UnlockedEvent) Payload() interface{} { + return e +} + +func (e *UnlockedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func NewUnlockedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *UnlockedEvent { + return &UnlockedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + UnlockedType, + ), + } +} + +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 ActivatedEvent struct { + *eventstore.BaseEvent `json:"-"` +} + +func (e *ActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) { + e.BaseEvent = event +} + +func (e *ActivatedEvent) Payload() interface{} { + return e +} + +func (e *ActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { + return nil +} + +func NewActivatedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *ActivatedEvent { + return &ActivatedEvent{ + BaseEvent: eventstore.NewBaseEventForPush( + ctx, + aggregate, + ActivatedType, + ), + } +} diff --git a/proto/zitadel/resources/user/v3alpha/user_service.proto b/proto/zitadel/resources/user/v3alpha/user_service.proto index d39ac1930..91831bdc4 100644 --- a/proto/zitadel/resources/user/v3alpha/user_service.proto +++ b/proto/zitadel/resources/user/v3alpha/user_service.proto @@ -150,7 +150,7 @@ service ZITADELUsers { // Returns the user identified by the requested ID. rpc GetUser (GetUserRequest) returns (GetUserResponse) { option (google.api.http) = { - get: "/resources/v3alpha/users/{user_id}" + get: "/resources/v3alpha/users/{id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -208,7 +208,7 @@ service ZITADELUsers { // Patch an existing user with data based on a user schema. rpc PatchUser (PatchUserRequest) returns (PatchUserResponse) { option (google.api.http) = { - patch: "/resources/v3alpha/users/{user_id}" + patch: "/resources/v3alpha/users/{id}" body: "user" }; @@ -238,7 +238,7 @@ service ZITADELUsers { // The endpoint returns an error if the user is already in the state 'deactivated'. rpc DeactivateUser (DeactivateUserRequest) returns (DeactivateUserResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/_deactivate" + post: "/resources/v3alpha/users/{id}/_deactivate" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -257,15 +257,15 @@ service ZITADELUsers { }; } - // Reactivate a user + // Activate a user // - // Reactivate a previously deactivated user and change the state to 'active'. + // Activate a previously deactivated user and change the state to 'active'. // The user will be able to log in again. // // The endpoint returns an error if the user is not in the state 'deactivated'. - rpc ReactivateUser (ReactivateUserRequest) returns (ReactivateUserResponse) { + rpc ActivateUser (ActivateUserRequest) returns (ActivateUserResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/_reactivate" + post: "/resources/v3alpha/users/{id}/_activate" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -278,7 +278,7 @@ service ZITADELUsers { responses: { key: "200"; value: { - description: "User successfully reactivated"; + description: "User successfully activated"; }; }; }; @@ -294,7 +294,7 @@ service ZITADELUsers { // The endpoint returns an error if the user is already in the state 'locked'. rpc LockUser (LockUserRequest) returns (LockUserResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/_lock" + post: "/resources/v3alpha/users/{id}/_lock" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -321,7 +321,7 @@ service ZITADELUsers { // The endpoint returns an error if the user is not in the state 'locked'. rpc UnlockUser (UnlockUserRequest) returns (UnlockUserResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/_unlock" + post: "/resources/v3alpha/users/{id}/_unlock" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -346,7 +346,7 @@ service ZITADELUsers { // The user will be able to log in anymore. rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}" + delete: "/resources/v3alpha/users/{id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -372,7 +372,7 @@ service ZITADELUsers { // which can be either returned or will be sent to the user by email. rpc SetContactEmail (SetContactEmailRequest) returns (SetContactEmailResponse) { option (google.api.http) = { - put: "/resources/v3alpha/users/{user_id}/email" + put: "/resources/v3alpha/users/{id}/email" body: "email" }; @@ -397,7 +397,7 @@ service ZITADELUsers { // Verify the contact email with the provided code. rpc VerifyContactEmail (VerifyContactEmailRequest) returns (VerifyContactEmailResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/email/_verify" + post: "/resources/v3alpha/users/{id}/email/_verify" body: "verification_code" }; @@ -422,7 +422,7 @@ service ZITADELUsers { // Resend the email with the verification code for the contact email address. rpc ResendContactEmailCode (ResendContactEmailCodeRequest) returns (ResendContactEmailCodeResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/email/_resend" + post: "/resources/v3alpha/users/{id}/email/_resend" body: "*" }; @@ -449,7 +449,7 @@ service ZITADELUsers { // which can be either returned or will be sent to the user by SMS. rpc SetContactPhone (SetContactPhoneRequest) returns (SetContactPhoneResponse) { option (google.api.http) = { - put: "/resources/v3alpha/users/{user_id}/phone" + put: "/resources/v3alpha/users/{id}/phone" body: "phone" }; @@ -474,7 +474,7 @@ service ZITADELUsers { // Verify the contact phone with the provided code. rpc VerifyContactPhone (VerifyContactPhoneRequest) returns (VerifyContactPhoneResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/phone/_verify" + post: "/resources/v3alpha/users/{id}/phone/_verify" body: "verification_code" }; @@ -499,7 +499,7 @@ service ZITADELUsers { // Resend the phone with the verification code for the contact phone number. rpc ResendContactPhoneCode (ResendContactPhoneCodeRequest) returns (ResendContactPhoneCodeResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/phone/_resend" + post: "/resources/v3alpha/users/{id}/phone/_resend" body: "*" }; @@ -524,7 +524,7 @@ service ZITADELUsers { // Add a new unique username to a user. The username will be used to identify the user on authentication. rpc AddUsername (AddUsernameRequest) returns (AddUsernameResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/username" + post: "/resources/v3alpha/users/{id}/username" body: "username" }; @@ -549,7 +549,7 @@ service ZITADELUsers { // Remove an existing username of a user, so it cannot be used for authentication anymore. rpc RemoveUsername (RemoveUsernameRequest) returns (RemoveUsernameResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/username/{username_id}" + delete: "/resources/v3alpha/users/{id}/username/{username_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -573,7 +573,7 @@ service ZITADELUsers { // Add, update or reset a user's password with either a verification code or the current password. rpc SetPassword (SetPasswordRequest) returns (SetPasswordResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/password" + post: "/resources/v3alpha/users/{id}/password" body: "new_password" }; @@ -598,7 +598,7 @@ service ZITADELUsers { // Request a code to be able to set a new password. rpc RequestPasswordReset (RequestPasswordResetRequest) returns (RequestPasswordResetResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/password/_reset" + post: "/resources/v3alpha/users/{id}/password/_reset" body: "*" }; @@ -625,7 +625,7 @@ service ZITADELUsers { // which are used to verify the device. rpc StartWebAuthNRegistration (StartWebAuthNRegistrationRequest) returns (StartWebAuthNRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/webauthn" + post: "/resources/v3alpha/users/{id}/webauthn" body: "registration" }; @@ -650,7 +650,7 @@ service ZITADELUsers { // Verify the WebAuthN registration started by StartWebAuthNRegistration with the public key credential. rpc VerifyWebAuthNRegistration (VerifyWebAuthNRegistrationRequest) returns (VerifyWebAuthNRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/webauthn/{web_auth_n_id}/_verify" + post: "/resources/v3alpha/users/{id}/webauthn/{web_auth_n_id}/_verify" body: "verify" }; @@ -675,7 +675,7 @@ service ZITADELUsers { // The code will allow the user to start a new WebAuthN registration. rpc CreateWebAuthNRegistrationLink (CreateWebAuthNRegistrationLinkRequest) returns (CreateWebAuthNRegistrationLinkResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/webauthn/registration_link" + post: "/resources/v3alpha/users/{id}/webauthn/registration_link" body: "*" }; @@ -699,7 +699,7 @@ service ZITADELUsers { // Remove an existing WebAuthN authenticator from a user, so it cannot be used for authentication anymore. rpc RemoveWebAuthNAuthenticator (RemoveWebAuthNAuthenticatorRequest) returns (RemoveWebAuthNAuthenticatorResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/webauthn/{web_auth_n_id}" + delete: "/resources/v3alpha/users/{id}/webauthn/{web_auth_n_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -723,7 +723,7 @@ service ZITADELUsers { // As a response a secret is returned, which is used to initialize a TOTP app or device. rpc StartTOTPRegistration (StartTOTPRegistrationRequest) returns (StartTOTPRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/totp" + post: "/resources/v3alpha/users/{id}/totp" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -746,7 +746,7 @@ service ZITADELUsers { // Verify the time-based one-time-password (TOTP) registration with the generated code. rpc VerifyTOTPRegistration (VerifyTOTPRegistrationRequest) returns (VerifyTOTPRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/totp/{totp_id}/_verify" + post: "/resources/v3alpha/users/{id}/totp/{totp_id}/_verify" body: "code" }; @@ -770,7 +770,7 @@ service ZITADELUsers { // Remove an existing time-based one-time-password (TOTP) authenticator from a user, so it cannot be used for authentication anymore. rpc RemoveTOTPAuthenticator (RemoveTOTPAuthenticatorRequest) returns (RemoveTOTPAuthenticatorResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/totp/{totp_id}" + delete: "/resources/v3alpha/users/{id}/totp/{totp_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -795,7 +795,7 @@ service ZITADELUsers { // which can be either returned or will be sent to the user by SMS. rpc AddOTPSMSAuthenticator (AddOTPSMSAuthenticatorRequest) returns (AddOTPSMSAuthenticatorResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/otp_sms" + post: "/resources/v3alpha/users/{id}/otp_sms" body: "phone" }; @@ -819,7 +819,7 @@ service ZITADELUsers { // Verify the OTP SMS registration with the provided code. rpc VerifyOTPSMSRegistration (VerifyOTPSMSRegistrationRequest) returns (VerifyOTPSMSRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/otp_sms/{otp_sms_id}/_verify" + post: "/resources/v3alpha/users/{id}/otp_sms/{otp_sms_id}/_verify" body: "code" }; @@ -844,7 +844,7 @@ service ZITADELUsers { // Remove an existing one-time-password (OTP) SMS authenticator from a user, so it cannot be used for authentication anymore. rpc RemoveOTPSMSAuthenticator (RemoveOTPSMSAuthenticatorRequest) returns (RemoveOTPSMSAuthenticatorResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/otp_sms/{otp_sms_id}" + delete: "/resources/v3alpha/users/{id}/otp_sms/{otp_sms_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -869,7 +869,7 @@ service ZITADELUsers { // which can be either returned or will be sent to the user by email. rpc AddOTPEmailAuthenticator (AddOTPEmailAuthenticatorRequest) returns (AddOTPEmailAuthenticatorResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/otp_email" + post: "/resources/v3alpha/users/{id}/otp_email" body: "email" }; @@ -893,7 +893,7 @@ service ZITADELUsers { // Verify the OTP Email registration with the provided code. rpc VerifyOTPEmailRegistration (VerifyOTPEmailRegistrationRequest) returns (VerifyOTPEmailRegistrationResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/otp_email/{otp_email_id}/_verify" + post: "/resources/v3alpha/users/{id}/otp_email/{otp_email_id}/_verify" body: "code" }; @@ -918,7 +918,7 @@ service ZITADELUsers { // Remove an existing one-time-password (OTP) Email authenticator from a user, so it cannot be used for authentication anymore. rpc RemoveOTPEmailAuthenticator (RemoveOTPEmailAuthenticatorRequest) returns (RemoveOTPEmailAuthenticatorResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/otp_email/{otp_email_id}" + delete: "/resources/v3alpha/users/{id}/otp_email/{otp_email_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -991,7 +991,7 @@ service ZITADELUsers { // This will allow the user to authenticate with the provided IDP. rpc AddIDPAuthenticator (AddIDPAuthenticatorRequest) returns (AddIDPAuthenticatorResponse) { option (google.api.http) = { - post: "/resources/v3alpha/users/{user_id}/idps" + post: "/resources/v3alpha/users/{id}/idps" body: "authenticator" }; @@ -1016,7 +1016,7 @@ service ZITADELUsers { // Remove an existing identity provider (IDP) authenticator from a user, so it cannot be used for authentication anymore. rpc RemoveIDPAuthenticator (RemoveIDPAuthenticatorRequest) returns (RemoveIDPAuthenticatorResponse) { option (google.api.http) = { - delete: "/resources/v3alpha/users/{user_id}/idps/{idp_id}" + delete: "/resources/v3alpha/users/{id}/idps/{idp_id}" }; option (zitadel.protoc_gen_zitadel.v2.options) = { @@ -1069,7 +1069,7 @@ message GetUserRequest { } ]; // unique identifier of the user. - string user_id = 2 [ + string id = 2 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1123,7 +1123,7 @@ message PatchUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"69629012906488334\""; } @@ -1156,7 +1156,7 @@ message DeactivateUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1172,7 +1172,7 @@ message DeactivateUserResponse { } -message ReactivateUserRequest { +message ActivateUserRequest { optional zitadel.object.v3alpha.Instance instance = 1 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { default: "\"domain from HOST or :authority header\"" @@ -1181,7 +1181,7 @@ message ReactivateUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1192,7 +1192,7 @@ message ReactivateUserRequest { ]; } -message ReactivateUserResponse { +message ActivateUserResponse { zitadel.resources.object.v3alpha.Details details = 1; } @@ -1205,7 +1205,7 @@ message LockUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1229,7 +1229,7 @@ message UnlockUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1253,7 +1253,7 @@ message DeleteUserRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1277,7 +1277,7 @@ message SetContactEmailRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1309,7 +1309,7 @@ message VerifyContactEmailRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1343,7 +1343,7 @@ message ResendContactEmailCodeRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1376,7 +1376,7 @@ message SetContactPhoneRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1408,7 +1408,7 @@ message VerifyContactPhoneRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1442,7 +1442,7 @@ message ResendContactPhoneCodeRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1475,7 +1475,7 @@ message AddUsernameRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1507,7 +1507,7 @@ message RemoveUsernameRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1541,7 +1541,7 @@ message SetPasswordRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1567,7 +1567,7 @@ message RequestPasswordResetRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1606,7 +1606,7 @@ message StartWebAuthNRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1645,7 +1645,7 @@ message VerifyWebAuthNRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1680,7 +1680,7 @@ message CreateWebAuthNRegistrationLinkRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1713,7 +1713,7 @@ message RemoveWebAuthNAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1747,7 +1747,7 @@ message StartTOTPRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1789,7 +1789,7 @@ message VerifyTOTPRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1833,7 +1833,7 @@ message RemoveTOTPAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1867,7 +1867,7 @@ message AddOTPSMSAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1906,7 +1906,7 @@ message VerifyOTPSMSRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1950,7 +1950,7 @@ message RemoveOTPSMSAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -1984,7 +1984,7 @@ message AddOTPEmailAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -2022,7 +2022,7 @@ message VerifyOTPEmailRegistrationRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -2066,7 +2066,7 @@ message RemoveOTPEmailAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -2170,7 +2170,7 @@ message GetIdentityProviderIntentResponse { // and detailed / profile information. IDPInformation idp_information = 2; // If the user was already federated and linked to a ZITADEL user, it's id will be returned. - optional string user_id = 3 [ + optional string id = 3 [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"163840776835432345\""; } @@ -2186,7 +2186,7 @@ message AddIDPAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -2211,7 +2211,7 @@ message RemoveIDPAuthenticatorRequest { // Optionally expect the user to be in this organization. optional zitadel.object.v3alpha.Organization organization = 2; // unique identifier of the user. - string user_id = 3 [ + string id = 3 [ (validate.rules).string = {min_len: 1, max_len: 200}, (google.api.field_behavior) = REQUIRED, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {