mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 01:47:33 +00:00
feat: add schema user create and remove (#8494)
# Which Problems Are Solved Added functionality that user with a userschema can be created and removed. # How the Problems Are Solved Added logic and moved APIs so that everything is API v3 conform. # Additional Changes - move of user and userschema API to resources folder - changed testing and parameters - some renaming # Additional Context closes #7308 --------- Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
51
internal/api/grpc/resources/user/v3alpha/server.go
Normal file
51
internal/api/grpc/resources/user/v3alpha/server.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
var _ user.ZITADELUsersServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
user.UnimplementedZITADELUsersServer
|
||||
command *command.Commands
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
userCodeAlg: userCodeAlg,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
user.RegisterZITADELUsersServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return user.ZITADELUsers_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return user.ZITADELUsers_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return user.ZITADELUsers_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return user.RegisterZITADELUsersHandler
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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
|
||||
Tester *integration.Tester
|
||||
Client user.ZITADELUsersClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, _, cancel := integration.Contexts(time.Hour)
|
||||
defer cancel()
|
||||
|
||||
Tester = integration.NewTester(ctx)
|
||||
defer Tester.Done()
|
||||
|
||||
IAMOwnerCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
|
||||
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
|
||||
UserCTX = Tester.WithAuthorization(ctx, integration.Login)
|
||||
Client = Tester.Client.UserV3Alpha
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
100*time.Millisecond,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
66
internal/api/grpc/resources/user/v3alpha/user.go
Normal file
66
internal/api/grpc/resources/user/v3alpha/user.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"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/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_ *user.CreateUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser, err := createUserRequestToCreateSchemaUser(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.command.CreateSchemaUser(ctx, schemauser, s.userCodeAlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreateUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.ResourceOwner),
|
||||
EmailCode: gu.Ptr(schemauser.ReturnCodeEmail),
|
||||
PhoneCode: gu.Ptr(schemauser.ReturnCodePhone),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUserRequest) (*command.CreateSchemaUser, error) {
|
||||
data, err := req.GetUser().GetData().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.CreateSchemaUser{
|
||||
ResourceOwner: authz.GetCtxData(ctx).OrgID,
|
||||
SchemaID: req.GetUser().GetSchemaId(),
|
||||
ID: req.GetUser().GetUserId(),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
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())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeleteUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkUserSchemaEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().UserSchema {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "TODO", "Errors.UserSchema.NotEnabled")
|
||||
}
|
@@ -0,0 +1,354 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/logging"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_CreateUser(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := Tester.CreateUserSchema(IAMOwnerCTX, schema)
|
||||
permissionSchema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "r",
|
||||
"self": "r"
|
||||
},
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
permissionSchemaResp := Tester.CreateUserSchema(IAMOwnerCTX, permissionSchema)
|
||||
orgResp := Tester.CreateOrganization(IAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCodeEmail bool
|
||||
returnCodePhone bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *user.CreateUserRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "user create, no schemaID",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{Data: unmarshalJSON("{\"name\": \"user\"}")},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user create, no context",
|
||||
ctx: context.Background(),
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: schemaResp.GetDetails().GetId(),
|
||||
Data: unmarshalJSON("{\"name\": \"user\"}"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user create, no permission",
|
||||
ctx: UserCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: schemaResp.GetDetails().GetId(),
|
||||
Data: unmarshalJSON("{\"name\": \"user\"}"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user create, invalid schema permission, owner",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: permissionSchemaResp.GetDetails().GetId(),
|
||||
Data: unmarshalJSON("{\"name\": \"user\"}"),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user create, no user data",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: schemaResp.GetDetails().GetId(),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "user create, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: schemaResp.GetDetails().GetId(),
|
||||
Data: unmarshalJSON("{\"name\": \"user\"}"),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "user create, full contact, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.CreateUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
User: &user.CreateUser{
|
||||
SchemaId: schemaResp.GetDetails().GetId(),
|
||||
Data: unmarshalJSON("{\"name\": \"user\"}"),
|
||||
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) {
|
||||
got, err := Tester.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_DeleteUser(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := Tester.CreateUserSchema(IAMOwnerCTX, schema)
|
||||
orgResp := Tester.CreateOrganization(IAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(ctx context.Context, req *user.DeleteUserRequest) error
|
||||
req *user.DeleteUserRequest
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "user delete, no userID",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.DeleteUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
UserId: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user delete, not existing",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &user.DeleteUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
UserId: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user delete, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(ctx context.Context, req *user.DeleteUserRequest) error {
|
||||
userResp := Tester.CreateSchemaUser(IAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.UserId = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.DeleteUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user delete, no permission",
|
||||
ctx: UserCTX,
|
||||
dep: func(ctx context.Context, req *user.DeleteUserRequest) error {
|
||||
userResp := Tester.CreateSchemaUser(IAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.UserId = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.DeleteUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user delete, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
dep: func(ctx context.Context, req *user.DeleteUserRequest) error {
|
||||
userResp := Tester.CreateSchemaUser(ctx, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.UserId = 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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.ctx, tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := Tester.Client.UserV3Alpha.DeleteUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalJSON(data string) *structpb.Struct {
|
||||
user := new(structpb.Struct)
|
||||
err := user.UnmarshalJSON([]byte(data))
|
||||
if err != nil {
|
||||
logging.OnError(err).Fatal("unmarshalling user json")
|
||||
}
|
||||
return user
|
||||
}
|
251
internal/api/grpc/resources/userschema/v3alpha/query.go
Normal file
251
internal/api/grpc/resources/userschema/v3alpha/query.go
Normal file
@@ -0,0 +1,251 @@
|
||||
package userschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SearchUserSchemas(ctx context.Context, req *schema.SearchUserSchemasRequest) (*schema.SearchUserSchemasResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := s.searchUserSchemaToModel(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUserSchema(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchemas, err := userSchemasToPb(res.UserSchemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.SearchUserSchemasResponse{
|
||||
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, res.SearchResponse),
|
||||
Result: userSchemas,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetUserSchemaByID(ctx context.Context, req *schema.GetUserSchemaByIDRequest) (*schema.GetUserSchemaByIDResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.GetUserSchemaByID(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := userSchemaToPb(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.GetUserSchemaByIDResponse{
|
||||
Schema: userSchema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) searchUserSchemaToModel(req *schema.SearchUserSchemasRequest) (*query.UserSchemaSearchQueries, error) {
|
||||
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := userSchemaFiltersToQuery(req.Filters, 0) // start at level 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserSchemaSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: userSchemaFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func userSchemaFieldNameToSortingColumn(field *schema.FieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.UserSchemaCreationDateCol
|
||||
}
|
||||
switch *field {
|
||||
case schema.FieldName_FIELD_NAME_TYPE:
|
||||
return query.UserSchemaTypeCol
|
||||
case schema.FieldName_FIELD_NAME_STATE:
|
||||
return query.UserSchemaStateCol
|
||||
case schema.FieldName_FIELD_NAME_REVISION:
|
||||
return query.UserSchemaRevisionCol
|
||||
case schema.FieldName_FIELD_NAME_CHANGE_DATE:
|
||||
return query.UserSchemaChangeDateCol
|
||||
case schema.FieldName_FIELD_NAME_CREATION_DATE:
|
||||
return query.UserSchemaCreationDateCol
|
||||
case schema.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
return query.UserSchemaIDCol
|
||||
default:
|
||||
return query.UserSchemaIDCol
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemasToPb(schemas []*query.UserSchema) (_ []*schema.UserSchema, err error) {
|
||||
userSchemas := make([]*schema.UserSchema, len(schemas))
|
||||
for i, userSchema := range schemas {
|
||||
userSchemas[i], err = userSchemaToPb(userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return userSchemas, nil
|
||||
}
|
||||
|
||||
func userSchemaToPb(userSchema *query.UserSchema) (*schema.UserSchema, error) {
|
||||
s := new(structpb.Struct)
|
||||
if err := s.UnmarshalJSON(userSchema.Schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.UserSchema{
|
||||
Details: resource_object.DomainToDetailsPb(&userSchema.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, userSchema.ResourceOwner),
|
||||
Type: userSchema.Type,
|
||||
State: userSchemaStateToPb(userSchema.State),
|
||||
Revision: userSchema.Revision,
|
||||
Schema: s,
|
||||
PossibleAuthenticators: authenticatorTypesToPb(userSchema.PossibleAuthenticators),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authenticatorTypesToPb(authenticators []domain.AuthenticatorType) []schema.AuthenticatorType {
|
||||
authTypes := make([]schema.AuthenticatorType, len(authenticators))
|
||||
for i, authenticator := range authenticators {
|
||||
authTypes[i] = authenticatorTypeToPb(authenticator)
|
||||
}
|
||||
return authTypes
|
||||
}
|
||||
|
||||
func authenticatorTypeToPb(authenticator domain.AuthenticatorType) schema.AuthenticatorType {
|
||||
switch authenticator {
|
||||
case domain.AuthenticatorTypeUsername:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME
|
||||
case domain.AuthenticatorTypePassword:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD
|
||||
case domain.AuthenticatorTypeWebAuthN:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN
|
||||
case domain.AuthenticatorTypeTOTP:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP
|
||||
case domain.AuthenticatorTypeOTPEmail:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL
|
||||
case domain.AuthenticatorTypeOTPSMS:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS
|
||||
case domain.AuthenticatorTypeAuthenticationKey:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY
|
||||
case domain.AuthenticatorTypeIdentityProvider:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER
|
||||
case domain.AuthenticatorTypeUnspecified:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED
|
||||
default:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaStateToPb(state domain.UserSchemaState) schema.State {
|
||||
switch state {
|
||||
case domain.UserSchemaStateActive:
|
||||
return schema.State_STATE_ACTIVE
|
||||
case domain.UserSchemaStateInactive:
|
||||
return schema.State_STATE_INACTIVE
|
||||
case domain.UserSchemaStateUnspecified,
|
||||
domain.UserSchemaStateDeleted:
|
||||
return schema.State_STATE_UNSPECIFIED
|
||||
default:
|
||||
return schema.State_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaStateToDomain(state schema.State) domain.UserSchemaState {
|
||||
switch state {
|
||||
case schema.State_STATE_ACTIVE:
|
||||
return domain.UserSchemaStateActive
|
||||
case schema.State_STATE_INACTIVE:
|
||||
return domain.UserSchemaStateInactive
|
||||
case schema.State_STATE_UNSPECIFIED:
|
||||
return domain.UserSchemaStateUnspecified
|
||||
default:
|
||||
return domain.UserSchemaStateUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaFiltersToQuery(queries []*schema.SearchFilter, level uint8) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = userSchemaFilterToQuery(query, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func userSchemaFilterToQuery(query *schema.SearchFilter, level uint8) (query.SearchQuery, error) {
|
||||
if level > 20 {
|
||||
// can't go deeper than 20 levels of nesting.
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-zsQ97", "Errors.Query.TooManyNestingLevels")
|
||||
}
|
||||
switch q := query.Filter.(type) {
|
||||
case *schema.SearchFilter_StateFilter:
|
||||
return stateQueryToQuery(q.StateFilter)
|
||||
case *schema.SearchFilter_TypeFilter:
|
||||
return typeQueryToQuery(q.TypeFilter)
|
||||
case *schema.SearchFilter_IdFilter:
|
||||
return idQueryToQuery(q.IdFilter)
|
||||
case *schema.SearchFilter_OrFilter:
|
||||
return orQueryToQuery(q.OrFilter, level)
|
||||
case *schema.SearchFilter_AndFilter:
|
||||
return andQueryToQuery(q.AndFilter, level)
|
||||
case *schema.SearchFilter_NotFilter:
|
||||
return notQueryToQuery(q.NotFilter, level)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func stateQueryToQuery(q *schema.StateFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaStateSearchQuery(userSchemaStateToDomain(q.GetState()))
|
||||
}
|
||||
|
||||
func typeQueryToQuery(q *schema.TypeFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaTypeSearchQuery(q.GetType(), resource_object.TextMethodPbToQuery(q.GetMethod()))
|
||||
}
|
||||
|
||||
func idQueryToQuery(q *schema.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaIDSearchQuery(q.GetId(), resource_object.TextMethodPbToQuery(q.GetMethod()))
|
||||
}
|
||||
|
||||
func orQueryToQuery(q *schema.OrFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := userSchemaFiltersToQuery(q.GetQueries(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserOrSearchQuery(mappedQueries)
|
||||
}
|
||||
|
||||
func andQueryToQuery(q *schema.AndFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := userSchemaFiltersToQuery(q.GetQueries(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserAndSearchQuery(mappedQueries)
|
||||
}
|
||||
|
||||
func notQueryToQuery(q *schema.NotFilter, level uint8) (query.SearchQuery, error) {
|
||||
mappedQuery, err := userSchemaFilterToQuery(q.GetFilter(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserNotSearchQuery(mappedQuery)
|
||||
}
|
@@ -0,0 +1,302 @@
|
||||
//go:build integration
|
||||
|
||||
package userschema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_ListUserSchemas(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.SearchUserSchemasRequest
|
||||
prepare func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.SearchUserSchemasResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not found, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{
|
||||
Filters: []*schema.SearchFilter{
|
||||
{
|
||||
Filter: &schema.SearchFilter_IdFilter{
|
||||
IdFilter: &schema.IDFilter{
|
||||
Id: "notexisting",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.SearchUserSchemasResponse{
|
||||
Details: &object.ListDetails{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*schema.UserSchema{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single (id), ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error {
|
||||
schemaType := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
createResp := Tester.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
request.Filters = []*schema.SearchFilter{
|
||||
{
|
||||
Filter: &schema.SearchFilter_IdFilter{
|
||||
IdFilter: &schema.IDFilter{
|
||||
Id: createResp.GetDetails().GetId(),
|
||||
Method: object.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
resp.Result[0].Type = schemaType
|
||||
resp.Result[0].Details = createResp.GetDetails()
|
||||
// as schema is freshly created, the changed date is the created date
|
||||
resp.Result[0].Details.Created = resp.Result[0].Details.GetChanged()
|
||||
resp.Details.Timestamp = resp.Result[0].Details.GetChanged()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.SearchUserSchemasResponse{
|
||||
Details: &object.ListDetails{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*schema.UserSchema{
|
||||
{
|
||||
State: schema.State_STATE_ACTIVE,
|
||||
Revision: 1,
|
||||
Schema: userSchema,
|
||||
PossibleAuthenticators: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple (type), ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.SearchUserSchemasRequest{},
|
||||
prepare: func(request *schema.SearchUserSchemasRequest, resp *schema.SearchUserSchemasResponse) error {
|
||||
schemaType := fmt.Sprint(time.Now().UnixNano())
|
||||
schemaType1 := schemaType + "_1"
|
||||
schemaType2 := schemaType + "_2"
|
||||
createResp := Tester.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType1)
|
||||
createResp2 := Tester.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType2)
|
||||
|
||||
request.SortingColumn = gu.Ptr(schema.FieldName_FIELD_NAME_TYPE)
|
||||
request.Query = &object.SearchQuery{Desc: false}
|
||||
request.Filters = []*schema.SearchFilter{
|
||||
{
|
||||
Filter: &schema.SearchFilter_TypeFilter{
|
||||
TypeFilter: &schema.TypeFilter{
|
||||
Type: schemaType,
|
||||
Method: object.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp.Result[0].Type = schemaType1
|
||||
resp.Result[0].Details = createResp.GetDetails()
|
||||
resp.Result[1].Type = schemaType2
|
||||
resp.Result[1].Details = createResp2.GetDetails()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.SearchUserSchemasResponse{
|
||||
Details: &object.ListDetails{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
Result: []*schema.UserSchema{
|
||||
{
|
||||
State: schema.State_STATE_ACTIVE,
|
||||
Revision: 1,
|
||||
Schema: userSchema,
|
||||
PossibleAuthenticators: nil,
|
||||
},
|
||||
{
|
||||
State: schema.State_STATE_ACTIVE,
|
||||
Revision: 1,
|
||||
Schema: userSchema,
|
||||
PossibleAuthenticators: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.prepare != nil {
|
||||
err := tt.args.prepare(tt.args.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
retryDuration := 20 * time.Second
|
||||
if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := Client.SearchUserSchemas(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(ttt, err)
|
||||
|
||||
// always first check length, otherwise its failed anyway
|
||||
assert.Len(ttt, got.Result, len(tt.want.Result))
|
||||
for i := range tt.want.Result {
|
||||
want := tt.want.Result[i]
|
||||
got := got.Result[i]
|
||||
|
||||
integration.AssertResourceDetails(t, want.GetDetails(), got.GetDetails())
|
||||
want.Details = got.Details
|
||||
grpc.AllFieldsEqual(t, want.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
}
|
||||
integration.AssertListDetails(t, tt.want, got)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected user schema result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_GetUserSchemaByID(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.GetUserSchemaByIDRequest
|
||||
prepare func(request *schema.GetUserSchemaByIDRequest, resp *schema.GetUserSchemaByIDResponse) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.GetUserSchemaByIDResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.GetUserSchemaByIDRequest{},
|
||||
prepare: func(request *schema.GetUserSchemaByIDRequest, resp *schema.GetUserSchemaByIDResponse) error {
|
||||
schemaType := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
createResp := Tester.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
request.Id = createResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.GetUserSchemaByIDRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "get, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.GetUserSchemaByIDRequest{},
|
||||
prepare: func(request *schema.GetUserSchemaByIDRequest, resp *schema.GetUserSchemaByIDResponse) error {
|
||||
schemaType := fmt.Sprint(time.Now().UnixNano() + 1)
|
||||
createResp := Tester.CreateUserSchemaEmptyWithType(IAMOwnerCTX, schemaType)
|
||||
request.Id = createResp.GetDetails().GetId()
|
||||
|
||||
resp.Schema.Type = schemaType
|
||||
resp.Schema.Details = createResp.GetDetails()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.GetUserSchemaByIDResponse{
|
||||
Schema: &schema.UserSchema{
|
||||
State: schema.State_STATE_ACTIVE,
|
||||
Revision: 1,
|
||||
Schema: userSchema,
|
||||
PossibleAuthenticators: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.args.prepare != nil {
|
||||
err := tt.args.prepare(tt.args.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
retryDuration := 5 * time.Second
|
||||
if ctxDeadline, ok := IAMOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := Client.GetUserSchemaByID(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(ttt, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.GetSchema().GetDetails(), got.GetSchema().GetDetails())
|
||||
tt.want.Schema.Details = got.GetSchema().GetDetails()
|
||||
grpc.AllFieldsEqual(t, tt.want.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
}, retryDuration, time.Millisecond*100, "timeout waiting for expected user schema result")
|
||||
})
|
||||
}
|
||||
}
|
65
internal/api/grpc/resources/userschema/v3alpha/server.go
Normal file
65
internal/api/grpc/resources/userschema/v3alpha/server.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package userschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
var _ schema.ZITADELUserSchemasServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
schema.UnimplementedZITADELUserSchemasServer
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
schema.RegisterZITADELUserSchemasServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return schema.ZITADELUserSchemas_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return schema.ZITADELUserSchemas_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return schema.ZITADELUserSchemas_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return schema.RegisterZITADELUserSchemasHandler
|
||||
}
|
||||
|
||||
func checkUserSchemaEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().UserSchema {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-SFjk3", "Errors.UserSchema.NotEnabled")
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
//go:build integration
|
||||
|
||||
package userschema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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
|
||||
Tester *integration.Tester
|
||||
Client schema.ZITADELUserSchemasClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(func() int {
|
||||
ctx, _, cancel := integration.Contexts(5 * time.Minute)
|
||||
defer cancel()
|
||||
|
||||
Tester = integration.NewTester(ctx)
|
||||
defer Tester.Done()
|
||||
|
||||
IAMOwnerCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
|
||||
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
|
||||
Client = Tester.Client.UserSchemaV3
|
||||
|
||||
return m.Run()
|
||||
}())
|
||||
}
|
||||
|
||||
func ensureFeatureEnabled(t *testing.T, iamOwnerCTX context.Context) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
UserSchema: gu.Ptr(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
retryDuration := time.Minute
|
||||
if ctxDeadline, ok := iamOwnerCTX.Deadline(); ok {
|
||||
retryDuration = time.Until(ctxDeadline)
|
||||
}
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(iamOwnerCTX, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
},
|
||||
retryDuration,
|
||||
100*time.Millisecond,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
}
|
149
internal/api/grpc/resources/userschema/v3alpha/userschema.go
Normal file
149
internal/api/grpc/resources/userschema/v3alpha/userschema.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package userschema
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) CreateUserSchema(ctx context.Context, req *schema.CreateUserSchemaRequest) (*schema.CreateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
userSchema, err := createUserSchemaToCommand(req, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.command.CreateUserSchema(ctx, userSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.CreateUserSchemaResponse{
|
||||
Details: resource_object.DomainToDetailsPb(userSchema.Details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) PatchUserSchema(ctx context.Context, req *schema.PatchUserSchemaRequest) (*schema.PatchUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
userSchema, err := patchUserSchemaToCommand(req, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.command.ChangeUserSchema(ctx, userSchema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.PatchUserSchemaResponse{
|
||||
Details: resource_object.DomainToDetailsPb(userSchema.Details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUserSchema(ctx context.Context, req *schema.DeactivateUserSchemaRequest) (*schema.DeactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.DeactivateUserSchema(ctx, req.GetId(), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeactivateUserSchemaResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUserSchema(ctx context.Context, req *schema.ReactivateUserSchemaRequest) (*schema.ReactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.ReactivateUserSchema(ctx, req.GetId(), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.ReactivateUserSchemaResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUserSchema(ctx context.Context, req *schema.DeleteUserSchemaRequest) (*schema.DeleteUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.DeleteUserSchema(ctx, req.GetId(), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeleteUserSchemaResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createUserSchemaToCommand(req *schema.CreateUserSchemaRequest, resourceOwner string) (*command.CreateUserSchema, error) {
|
||||
schema, err := req.GetUserSchema().GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.CreateUserSchema{
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.GetUserSchema().GetType(),
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetUserSchema().GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func patchUserSchemaToCommand(req *schema.PatchUserSchemaRequest, resourceOwner string) (*command.ChangeUserSchema, error) {
|
||||
schema, err := req.GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.ChangeUserSchema{
|
||||
ID: req.GetId(),
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.Type,
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authenticatorsToDomain(authenticators []schema.AuthenticatorType) []domain.AuthenticatorType {
|
||||
types := make([]domain.AuthenticatorType, len(authenticators))
|
||||
for i, authenticator := range authenticators {
|
||||
types[i] = authenticatorTypeToDomain(authenticator)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func authenticatorTypeToDomain(authenticator schema.AuthenticatorType) domain.AuthenticatorType {
|
||||
switch authenticator {
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME:
|
||||
return domain.AuthenticatorTypeUsername
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD:
|
||||
return domain.AuthenticatorTypePassword
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN:
|
||||
return domain.AuthenticatorTypeWebAuthN
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP:
|
||||
return domain.AuthenticatorTypeTOTP
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL:
|
||||
return domain.AuthenticatorTypeOTPEmail
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS:
|
||||
return domain.AuthenticatorTypeOTPSMS
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY:
|
||||
return domain.AuthenticatorTypeAuthenticationKey
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER:
|
||||
return domain.AuthenticatorTypeIdentityProvider
|
||||
default:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
}
|
||||
}
|
@@ -0,0 +1,811 @@
|
||||
//go:build integration
|
||||
|
||||
package userschema_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_CreateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *schema.CreateUserSchemaRequest
|
||||
want *schema.CreateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission, error",
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: "",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty schema, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no authenticators, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "with authenticator, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with invalid permission, error",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": "read"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "with valid permission, ok",
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.CreateUserSchemaRequest{
|
||||
UserSchema: &schema.CreateUserSchema{
|
||||
Type: gofakeit.Name(),
|
||||
DataType: &schema.CreateUserSchema_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"urn:zitadel:schema:permission": {
|
||||
"owner": "rw",
|
||||
"self": "r"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.CreateUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.CreateUserSchema(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.GetDetails(), got.GetDetails())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_UpdateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.PatchUserSchemaRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *schema.PatchUserSchemaRequest) error
|
||||
args args
|
||||
want *schema.PatchUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing id, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
request.Id = "notexisting"
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
Type: gu.Ptr(""),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update type, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
want: &schema.PatchUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty schema, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
DataType: &schema.PatchUserSchemaRequest_Schema{},
|
||||
},
|
||||
},
|
||||
want: &schema.PatchUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid schema, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
DataType: &schema.PatchUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update schema, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
DataType: &schema.PatchUserSchemaRequest_Schema{
|
||||
Schema: func() *structpb.Struct {
|
||||
s := new(structpb.Struct)
|
||||
err := s.UnmarshalJSON([]byte(`
|
||||
{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.PatchUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid authenticator, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "update authenticator, ok",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
PossibleAuthenticators: []schema.AuthenticatorType{
|
||||
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &schema.PatchUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inactive, error",
|
||||
prepare: func(request *schema.PatchUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.PatchUserSchemaRequest{
|
||||
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.PatchUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want.GetDetails(), got.GetDetails())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeactivateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.DeactivateUserSchemaRequest
|
||||
prepare func(request *schema.DeactivateUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.DeactivateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "active, ok",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.DeactivateUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inactive, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
&schema.DeactivateUserSchemaRequest{},
|
||||
func(request *schema.DeactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want.GetDetails(), got.GetDetails())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ReactivateUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.ReactivateUserSchemaRequest
|
||||
prepare func(request *schema.ReactivateUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.ReactivateUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
&schema.ReactivateUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.ReactivateUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "active, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "inactive, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.ReactivateUserSchemaRequest{},
|
||||
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeactivateUserSchema(IAMOwnerCTX, &schema.DeactivateUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &schema.ReactivateUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.ReactivateUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want.GetDetails(), got.GetDetails())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteUserSchema(t *testing.T) {
|
||||
ensureFeatureEnabled(t, IAMOwnerCTX)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *schema.DeleteUserSchemaRequest
|
||||
prepare func(request *schema.DeleteUserSchemaRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *schema.DeleteUserSchemaResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "not existing, error",
|
||||
args: args{
|
||||
IAMOwnerCTX,
|
||||
&schema.DeleteUserSchemaRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
func(request *schema.DeleteUserSchemaRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete, ok",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
return nil
|
||||
},
|
||||
},
|
||||
want: &schema.DeleteUserSchemaResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: Tester.Instance.InstanceID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deleted, error",
|
||||
args: args{
|
||||
ctx: IAMOwnerCTX,
|
||||
req: &schema.DeleteUserSchemaRequest{},
|
||||
prepare: func(request *schema.DeleteUserSchemaRequest) error {
|
||||
schemaID := Tester.CreateUserSchemaEmpty(IAMOwnerCTX).GetDetails().GetId()
|
||||
request.Id = schemaID
|
||||
_, err := Client.DeleteUserSchema(IAMOwnerCTX, &schema.DeleteUserSchemaRequest{
|
||||
Id: schemaID,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := Client.DeleteUserSchema(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want.GetDetails(), got.GetDetails())
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,386 +0,0 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) CreateUserSchema(ctx context.Context, req *schema.CreateUserSchemaRequest) (*schema.CreateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := createUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, details, err := s.command.CreateUserSchema(ctx, userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.CreateUserSchemaResponse{
|
||||
Id: id,
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateUserSchema(ctx context.Context, req *schema.UpdateUserSchemaRequest) (*schema.UpdateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := updateUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.UpdateUserSchema(ctx, userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.UpdateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUserSchema(ctx context.Context, req *schema.DeactivateUserSchemaRequest) (*schema.DeactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeactivateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUserSchema(ctx context.Context, req *schema.ReactivateUserSchemaRequest) (*schema.ReactivateUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.ReactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.ReactivateUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUserSchema(ctx context.Context, req *schema.DeleteUserSchemaRequest) (*schema.DeleteUserSchemaResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.DeleteUserSchemaResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListUserSchemas(ctx context.Context, req *schema.ListUserSchemasRequest) (*schema.ListUserSchemasResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := listUserSchemaToQuery(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUserSchema(ctx, queries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchemas, err := userSchemasToPb(res.UserSchemas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.ListUserSchemasResponse{
|
||||
Details: object.ToListDetails(res.SearchResponse),
|
||||
Result: userSchemas,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetUserSchemaByID(ctx context.Context, req *schema.GetUserSchemaByIDRequest) (*schema.GetUserSchemaByIDResponse, error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.GetUserSchemaByID(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userSchema, err := userSchemaToPb(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.GetUserSchemaByIDResponse{
|
||||
Schema: userSchema,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func userSchemasToPb(schemas []*query.UserSchema) (_ []*schema.UserSchema, err error) {
|
||||
userSchemas := make([]*schema.UserSchema, len(schemas))
|
||||
for i, userSchema := range schemas {
|
||||
userSchemas[i], err = userSchemaToPb(userSchema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return userSchemas, nil
|
||||
}
|
||||
|
||||
func userSchemaToPb(userSchema *query.UserSchema) (*schema.UserSchema, error) {
|
||||
s := new(structpb.Struct)
|
||||
if err := s.UnmarshalJSON(userSchema.Schema); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &schema.UserSchema{
|
||||
Id: userSchema.ID,
|
||||
Details: object.DomainToDetailsPb(&userSchema.ObjectDetails),
|
||||
Type: userSchema.Type,
|
||||
State: userSchemaStateToPb(userSchema.State),
|
||||
Revision: userSchema.Revision,
|
||||
Schema: s,
|
||||
PossibleAuthenticators: authenticatorTypesToPb(userSchema.PossibleAuthenticators),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authenticatorTypesToPb(authenticators []domain.AuthenticatorType) []schema.AuthenticatorType {
|
||||
authTypes := make([]schema.AuthenticatorType, len(authenticators))
|
||||
for i, authenticator := range authenticators {
|
||||
authTypes[i] = authenticatorTypeToPb(authenticator)
|
||||
}
|
||||
return authTypes
|
||||
}
|
||||
|
||||
func authenticatorTypeToPb(authenticator domain.AuthenticatorType) schema.AuthenticatorType {
|
||||
switch authenticator {
|
||||
case domain.AuthenticatorTypeUsername:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME
|
||||
case domain.AuthenticatorTypePassword:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD
|
||||
case domain.AuthenticatorTypeWebAuthN:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN
|
||||
case domain.AuthenticatorTypeTOTP:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP
|
||||
case domain.AuthenticatorTypeOTPEmail:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL
|
||||
case domain.AuthenticatorTypeOTPSMS:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS
|
||||
case domain.AuthenticatorTypeAuthenticationKey:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY
|
||||
case domain.AuthenticatorTypeIdentityProvider:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER
|
||||
case domain.AuthenticatorTypeUnspecified:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED
|
||||
default:
|
||||
return schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaStateToPb(state domain.UserSchemaState) schema.State {
|
||||
switch state {
|
||||
case domain.UserSchemaStateActive:
|
||||
return schema.State_STATE_ACTIVE
|
||||
case domain.UserSchemaStateInactive:
|
||||
return schema.State_STATE_INACTIVE
|
||||
case domain.UserSchemaStateUnspecified,
|
||||
domain.UserSchemaStateDeleted:
|
||||
return schema.State_STATE_UNSPECIFIED
|
||||
default:
|
||||
return schema.State_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func listUserSchemaToQuery(req *schema.ListUserSchemasRequest) (*query.UserSchemaSearchQueries, error) {
|
||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
||||
queries, err := userSchemaQueriesToQuery(req.Queries, 0) // start at level 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserSchemaSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: userSchemaFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func userSchemaFieldNameToSortingColumn(column schema.FieldName) query.Column {
|
||||
switch column {
|
||||
case schema.FieldName_FIELD_NAME_TYPE:
|
||||
return query.UserSchemaTypeCol
|
||||
case schema.FieldName_FIELD_NAME_STATE:
|
||||
return query.UserSchemaStateCol
|
||||
case schema.FieldName_FIELD_NAME_REVISION:
|
||||
return query.UserSchemaRevisionCol
|
||||
case schema.FieldName_FIELD_NAME_CHANGE_DATE:
|
||||
return query.UserSchemaChangeDateCol
|
||||
case schema.FieldName_FIELD_NAME_UNSPECIFIED:
|
||||
return query.UserSchemaIDCol
|
||||
default:
|
||||
return query.UserSchemaIDCol
|
||||
}
|
||||
}
|
||||
|
||||
func checkUserSchemaEnabled(ctx context.Context) error {
|
||||
if authz.GetInstance(ctx).Features().UserSchema {
|
||||
return nil
|
||||
}
|
||||
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-SFjk3", "Errors.UserSchema.NotEnabled")
|
||||
}
|
||||
|
||||
func createUserSchemaToCommand(req *schema.CreateUserSchemaRequest, resourceOwner string) (*command.CreateUserSchema, error) {
|
||||
schema, err := req.GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.CreateUserSchema{
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.GetType(),
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateUserSchemaToCommand(req *schema.UpdateUserSchemaRequest, resourceOwner string) (*command.UpdateUserSchema, error) {
|
||||
schema, err := req.GetSchema().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.UpdateUserSchema{
|
||||
ID: req.GetId(),
|
||||
ResourceOwner: resourceOwner,
|
||||
Type: req.Type,
|
||||
Schema: schema,
|
||||
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func authenticatorsToDomain(authenticators []schema.AuthenticatorType) []domain.AuthenticatorType {
|
||||
types := make([]domain.AuthenticatorType, len(authenticators))
|
||||
for i, authenticator := range authenticators {
|
||||
types[i] = authenticatorTypeToDomain(authenticator)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
func authenticatorTypeToDomain(authenticator schema.AuthenticatorType) domain.AuthenticatorType {
|
||||
switch authenticator {
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME:
|
||||
return domain.AuthenticatorTypeUsername
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD:
|
||||
return domain.AuthenticatorTypePassword
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN:
|
||||
return domain.AuthenticatorTypeWebAuthN
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP:
|
||||
return domain.AuthenticatorTypeTOTP
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL:
|
||||
return domain.AuthenticatorTypeOTPEmail
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS:
|
||||
return domain.AuthenticatorTypeOTPSMS
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY:
|
||||
return domain.AuthenticatorTypeAuthenticationKey
|
||||
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER:
|
||||
return domain.AuthenticatorTypeIdentityProvider
|
||||
default:
|
||||
return domain.AuthenticatorTypeUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaStateToDomain(state schema.State) domain.UserSchemaState {
|
||||
switch state {
|
||||
case schema.State_STATE_ACTIVE:
|
||||
return domain.UserSchemaStateActive
|
||||
case schema.State_STATE_INACTIVE:
|
||||
return domain.UserSchemaStateInactive
|
||||
case schema.State_STATE_UNSPECIFIED:
|
||||
return domain.UserSchemaStateUnspecified
|
||||
default:
|
||||
return domain.UserSchemaStateUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func userSchemaQueriesToQuery(queries []*schema.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = userSchemaQueryToQuery(query, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func userSchemaQueryToQuery(query *schema.SearchQuery, level uint8) (query.SearchQuery, error) {
|
||||
if level > 20 {
|
||||
// can't go deeper than 20 levels of nesting.
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-zsQ97", "Errors.Query.TooManyNestingLevels")
|
||||
}
|
||||
switch q := query.Query.(type) {
|
||||
case *schema.SearchQuery_StateQuery:
|
||||
return stateQueryToQuery(q.StateQuery)
|
||||
case *schema.SearchQuery_TypeQuery:
|
||||
return typeQueryToQuery(q.TypeQuery)
|
||||
case *schema.SearchQuery_IdQuery:
|
||||
return idQueryToQuery(q.IdQuery)
|
||||
case *schema.SearchQuery_OrQuery:
|
||||
return orQueryToQuery(q.OrQuery, level)
|
||||
case *schema.SearchQuery_AndQuery:
|
||||
return andQueryToQuery(q.AndQuery, level)
|
||||
case *schema.SearchQuery_NotQuery:
|
||||
return notQueryToQuery(q.NotQuery, level)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func stateQueryToQuery(q *schema.StateQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaStateSearchQuery(userSchemaStateToDomain(q.GetState()))
|
||||
}
|
||||
|
||||
func typeQueryToQuery(q *schema.TypeQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaTypeSearchQuery(q.GetType(), object.TextMethodToQuery(q.GetMethod()))
|
||||
}
|
||||
|
||||
func idQueryToQuery(q *schema.IDQuery) (query.SearchQuery, error) {
|
||||
return query.NewUserSchemaIDSearchQuery(q.GetId(), object.TextMethodToQuery(q.GetMethod()))
|
||||
}
|
||||
|
||||
func orQueryToQuery(q *schema.OrQuery, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := userSchemaQueriesToQuery(q.GetQueries(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserOrSearchQuery(mappedQueries)
|
||||
}
|
||||
|
||||
func andQueryToQuery(q *schema.AndQuery, level uint8) (query.SearchQuery, error) {
|
||||
mappedQueries, err := userSchemaQueriesToQuery(q.GetQueries(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserAndSearchQuery(mappedQueries)
|
||||
}
|
||||
|
||||
func notQueryToQuery(q *schema.NotQuery, level uint8) (query.SearchQuery, error) {
|
||||
mappedQuery, err := userSchemaQueryToQuery(q.GetQuery(), level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return query.NewUserNotSearchQuery(mappedQuery)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,51 +0,0 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
)
|
||||
|
||||
var _ schema.UserSchemaServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
schema.UnimplementedUserSchemaServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
schema.RegisterUserSchemaServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return schema.UserSchemaService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return schema.UserSchemaService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return schema.UserSchemaService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return schema.RegisterUserSchemaServiceHandler
|
||||
}
|
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
type CreateUserSchema struct {
|
||||
Details *domain.ObjectDetails
|
||||
|
||||
ResourceOwner string
|
||||
Type string
|
||||
Schema json.RawMessage
|
||||
@@ -33,7 +35,9 @@ func (s *CreateUserSchema) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UpdateUserSchema struct {
|
||||
type ChangeUserSchema struct {
|
||||
Details *domain.ObjectDetails
|
||||
|
||||
ID string
|
||||
ResourceOwner string
|
||||
Type *string
|
||||
@@ -41,7 +45,7 @@ type UpdateUserSchema struct {
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
}
|
||||
|
||||
func (s *UpdateUserSchema) Valid() error {
|
||||
func (s *ChangeUserSchema) Valid() error {
|
||||
if s.ID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing")
|
||||
}
|
||||
@@ -59,40 +63,43 @@ func (s *UpdateUserSchema) Valid() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateUserSchema(ctx context.Context, userSchema *CreateUserSchema) (string, *domain.ObjectDetails, error) {
|
||||
func (c *Commands) CreateUserSchema(ctx context.Context, userSchema *CreateUserSchema) error {
|
||||
if err := userSchema.Valid(); err != nil {
|
||||
return "", nil, err
|
||||
return err
|
||||
}
|
||||
if userSchema.ResourceOwner == "" {
|
||||
return "", nil, zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing")
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMA-J3hhj", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return err
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, userSchema.ResourceOwner)
|
||||
err = c.pushAppendAndReduce(ctx, writeModel,
|
||||
writeModel, err := c.getSchemaWriteModelByID(ctx, userSchema.ResourceOwner, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewCreatedEvent(ctx,
|
||||
UserSchemaAggregateFromWriteModel(&writeModel.WriteModel),
|
||||
userSchema.Type, userSchema.Schema, userSchema.PossibleAuthenticators,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return id, writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) UpdateUserSchema(ctx context.Context, userSchema *UpdateUserSchema) (*domain.ObjectDetails, error) {
|
||||
func (c *Commands) ChangeUserSchema(ctx context.Context, userSchema *ChangeUserSchema) error {
|
||||
if err := userSchema.Valid(); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(userSchema.ID, userSchema.ResourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
writeModel, err := c.getSchemaWriteModelByID(ctx, userSchema.ResourceOwner, userSchema.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive")
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMA-HB3e1", "Errors.UserSchema.NotActive")
|
||||
}
|
||||
updatedEvent := writeModel.NewUpdatedEvent(
|
||||
ctx,
|
||||
@@ -102,29 +109,30 @@ func (c *Commands) UpdateUserSchema(ctx context.Context, userSchema *UpdateUserS
|
||||
userSchema.PossibleAuthenticators,
|
||||
)
|
||||
if updatedEvent == nil {
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
|
||||
return nil
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, updatedEvent); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
userSchema.Details = writeModelToObjectDetails(&writeModel.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeactivateUserSchema(ctx context.Context, id, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-Vvf3w", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateActive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-E4t4z", "Errors.UserSchema.NotActive")
|
||||
}
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewDeactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
)
|
||||
if err != nil {
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
@@ -134,17 +142,16 @@ func (c *Commands) ReactivateUserSchema(ctx context.Context, id, resourceOwner s
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-wq3Gw", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State != domain.UserSchemaStateInactive {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-DGzh5", "Errors.UserSchema.NotInactive")
|
||||
}
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewReactivatedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
)
|
||||
if err != nil {
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
@@ -154,18 +161,17 @@ func (c *Commands) DeleteUserSchema(ctx context.Context, id, resourceOwner strin
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMA-E22gg", "Errors.IDMissing")
|
||||
}
|
||||
writeModel := NewUserSchemaWriteModel(id, resourceOwner)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
writeModel, err := c.getSchemaWriteModelByID(ctx, resourceOwner, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMA-Grg41", "Errors.UserSchema.NotExists")
|
||||
}
|
||||
// TODO: check for users based on that schema; this is only possible with / after https://github.com/zitadel/zitadel/issues/7308
|
||||
err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schema.NewDeletedEvent(ctx, UserSchemaAggregateFromWriteModel(&writeModel.WriteModel), writeModel.SchemaType),
|
||||
)
|
||||
if err != nil {
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToObjectDetails(&writeModel.WriteModel), nil
|
||||
@@ -178,3 +184,11 @@ func validateUserSchema(userSchema json.RawMessage) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaWriteModelByID(ctx context.Context, resourceOwner, id string) (*UserSchemaWriteModel, error) {
|
||||
writeModel := NewUserSchemaWriteModel(resourceOwner, id, "")
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
||||
|
@@ -19,14 +19,16 @@ type UserSchemaWriteModel struct {
|
||||
Schema json.RawMessage
|
||||
PossibleAuthenticators []domain.AuthenticatorType
|
||||
State domain.UserSchemaState
|
||||
Revision uint64
|
||||
}
|
||||
|
||||
func NewUserSchemaWriteModel(schemaID, resourceOwner string) *UserSchemaWriteModel {
|
||||
func NewUserSchemaWriteModel(resourceOwner, schemaID, ty string) *UserSchemaWriteModel {
|
||||
return &UserSchemaWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: schemaID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
SchemaType: ty,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +40,14 @@ func (wm *UserSchemaWriteModel) Reduce() error {
|
||||
wm.Schema = e.Schema
|
||||
wm.PossibleAuthenticators = e.PossibleAuthenticators
|
||||
wm.State = domain.UserSchemaStateActive
|
||||
wm.Revision = 1
|
||||
case *schema.UpdatedEvent:
|
||||
if e.SchemaType != nil {
|
||||
wm.SchemaType = *e.SchemaType
|
||||
}
|
||||
if e.SchemaRevision != nil {
|
||||
wm.Revision = *e.SchemaRevision
|
||||
}
|
||||
if len(e.Schema) > 0 {
|
||||
wm.Schema = e.Schema
|
||||
}
|
||||
@@ -60,7 +66,7 @@ func (wm *UserSchemaWriteModel) Reduce() error {
|
||||
}
|
||||
|
||||
func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(schema.AggregateType).
|
||||
@@ -71,8 +77,13 @@ func (wm *UserSchemaWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
schema.DeactivatedType,
|
||||
schema.ReactivatedType,
|
||||
schema.DeletedType,
|
||||
).
|
||||
Builder()
|
||||
)
|
||||
|
||||
if wm.SchemaType != "" {
|
||||
query = query.EventData(map[string]interface{}{"schemaType": wm.SchemaType})
|
||||
}
|
||||
|
||||
return query.Builder()
|
||||
}
|
||||
func (wm *UserSchemaWriteModel) NewUpdatedEvent(
|
||||
ctx context.Context,
|
||||
@@ -87,6 +98,8 @@ 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))
|
||||
}
|
||||
if len(possibleAuthenticators) > 0 && slices.Compare(wm.PossibleAuthenticators, possibleAuthenticators) != 0 {
|
||||
changes = append(changes, schema.ChangePossibleAuthenticators(possibleAuthenticators))
|
||||
|
@@ -27,7 +27,6 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
userSchema *CreateUserSchema
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
details *domain.ObjectDetails
|
||||
err error
|
||||
}
|
||||
@@ -107,6 +106,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
"empty user schema created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
@@ -131,8 +131,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ID: "id1",
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
@@ -141,6 +141,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
"user schema created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
@@ -181,8 +182,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ID: "id1",
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
@@ -220,6 +221,7 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
"user schema with permission created",
|
||||
fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
schema.NewCreatedEvent(
|
||||
context.Background(),
|
||||
@@ -266,8 +268,8 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
},
|
||||
},
|
||||
res{
|
||||
id: "id1",
|
||||
details: &domain.ObjectDetails{
|
||||
ID: "id1",
|
||||
ResourceOwner: "instanceID",
|
||||
},
|
||||
},
|
||||
@@ -279,21 +281,20 @@ func TestCommands_CreateUserSchema(t *testing.T) {
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
}
|
||||
gotID, gotDetails, err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
assert.Equal(t, tt.res.id, gotID)
|
||||
assertObjectDetails(t, tt.res.details, gotDetails)
|
||||
err := c.CreateUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
assertObjectDetails(t, tt.res.details, tt.args.userSchema.Details)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
func TestCommands_ChangeUserSchema(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
userSchema *UpdateUserSchema
|
||||
userSchema *ChangeUserSchema
|
||||
}
|
||||
type res struct {
|
||||
details *domain.ObjectDetails
|
||||
@@ -312,7 +313,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{},
|
||||
userSchema: &ChangeUserSchema{},
|
||||
},
|
||||
res{
|
||||
err: zerrors.ThrowInvalidArgument(nil, "COMMA-H5421", "Errors.IDMissing"),
|
||||
@@ -325,7 +326,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr(""),
|
||||
},
|
||||
@@ -341,7 +342,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
},
|
||||
},
|
||||
@@ -356,7 +357,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{
|
||||
"properties": {
|
||||
@@ -379,7 +380,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
@@ -400,7 +401,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr("type"),
|
||||
Schema: json.RawMessage(`{}`),
|
||||
@@ -432,7 +433,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Type: gu.Ptr("type"),
|
||||
Schema: json.RawMessage(`{}`),
|
||||
@@ -473,7 +474,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
Type: gu.Ptr("newType"),
|
||||
@@ -515,7 +516,9 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
schema.NewUpdatedEvent(
|
||||
context.Background(),
|
||||
&schema.NewAggregate("id1", "instanceID").Aggregate,
|
||||
[]schema.Changes{schema.ChangeSchema(json.RawMessage(`{
|
||||
[]schema.Changes{
|
||||
schema.IncreaseRevision(1),
|
||||
schema.ChangeSchema(json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -539,7 +542,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
@@ -597,7 +600,7 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
},
|
||||
args{
|
||||
ctx: authz.NewMockContext("instanceID", "", ""),
|
||||
userSchema: &UpdateUserSchema{
|
||||
userSchema: &ChangeUserSchema{
|
||||
ID: "id1",
|
||||
Schema: json.RawMessage(`{}`),
|
||||
PossibleAuthenticators: []domain.AuthenticatorType{
|
||||
@@ -618,9 +621,9 @@ func TestCommands_UpdateUserSchema(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
}
|
||||
got, err := c.UpdateUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
err := c.ChangeUserSchema(tt.args.ctx, tt.args.userSchema)
|
||||
assert.ErrorIs(t, err, tt.res.err)
|
||||
assertObjectDetails(t, tt.res.details, got)
|
||||
assertObjectDetails(t, tt.res.details, tt.args.userSchema.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
220
internal/command/user_v3.go
Normal file
220
internal/command/user_v3.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
domain_schema "github.com/zitadel/zitadel/internal/domain/schema"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
type CreateSchemaUser struct {
|
||||
Details *domain.ObjectDetails
|
||||
ResourceOwner string
|
||||
|
||||
SchemaID string
|
||||
schemaRevision uint64
|
||||
|
||||
ID string
|
||||
Data json.RawMessage
|
||||
|
||||
Email *Email
|
||||
ReturnCodeEmail string
|
||||
Phone *Phone
|
||||
ReturnCodePhone string
|
||||
}
|
||||
|
||||
func (s *CreateSchemaUser) Valid(ctx context.Context, c *Commands) (err error) {
|
||||
if s.ResourceOwner == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-urEJKa1tJM", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if s.SchemaID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "COMMAND-TFo06JgnF2", "Errors.UserSchema.ID.Missing")
|
||||
}
|
||||
|
||||
schemaWriteModel, err := c.getSchemaWriteModelByID(ctx, "", s.SchemaID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !schemaWriteModel.Exists() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-N9QOuN4F7o", "Errors.UserSchema.NotExists")
|
||||
}
|
||||
s.schemaRevision = schemaWriteModel.Revision
|
||||
|
||||
if s.ID == "" {
|
||||
s.ID, err = c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// get role for permission check in schema through extension
|
||||
role, err := c.getSchemaRoleForWrite(ctx, s.ResourceOwner, s.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schema, err := domain_schema.NewSchema(role, bytes.NewReader(schemaWriteModel.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
if err := json.Unmarshal(s.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")
|
||||
}
|
||||
|
||||
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 (c *Commands) getSchemaRoleForWrite(ctx context.Context, resourceOwner, userID string) (domain_schema.Role, error) {
|
||||
if userID == authz.GetCtxData(ctx).UserID {
|
||||
return domain_schema.RoleSelf, nil
|
||||
}
|
||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil {
|
||||
return domain_schema.RoleUnspecified, err
|
||||
}
|
||||
return domain_schema.RoleOwner, nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateSchemaUser(ctx context.Context, user *CreateSchemaUser, alg crypto.EncryptionAlgorithm) (err error) {
|
||||
if err := user.Valid(ctx, c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
writeModel, err := c.getSchemaUserExists(ctx, user.ResourceOwner, user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if writeModel.Exists() {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-Nn8CRVlkeZ", "Errors.User.AlreadyExists")
|
||||
}
|
||||
|
||||
userAgg := UserV3AggregateFromWriteModel(&writeModel.WriteModel)
|
||||
events := []eventstore.Command{
|
||||
schemauser.NewCreatedEvent(ctx,
|
||||
userAgg,
|
||||
user.SchemaID, user.schemaRevision, user.Data,
|
||||
),
|
||||
}
|
||||
if user.Email != nil {
|
||||
events, user.ReturnCodeEmail, err = c.updateSchemaUserEmail(ctx, 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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel, events...); err != nil {
|
||||
return err
|
||||
}
|
||||
user.Details = writeModelToObjectDetails(&writeModel.WriteModel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) DeleteSchemaUser(ctx context.Context, id string) (*domain.ObjectDetails, error) {
|
||||
if id == "" {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Vs4wJCME7T", "Errors.IDMissing")
|
||||
}
|
||||
writeModel, err := c.getSchemaUserExists(ctx, "", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !writeModel.Exists() {
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-syHyCsGmvM", "Errors.User.NotFound")
|
||||
}
|
||||
if err := c.checkPermissionDeleteUser(ctx, writeModel.ResourceOwner, writeModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := c.pushAppendAndReduce(ctx, writeModel,
|
||||
schemauser.NewDeletedEvent(ctx, UserV3AggregateFromWriteModel(&writeModel.WriteModel)),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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) {
|
||||
|
||||
events = append(events, schemauser.NewEmailUpdatedEvent(ctx,
|
||||
agg,
|
||||
email.Address,
|
||||
))
|
||||
if email.Verified {
|
||||
events = append(events, schemauser.NewEmailVerifiedEvent(ctx, agg))
|
||||
} else {
|
||||
cryptoCode, err := c.newEmailCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if email.ReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
events = append(events, schemauser.NewEmailCodeAddedEvent(ctx, agg,
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
email.URLTemplate,
|
||||
email.ReturnCode,
|
||||
))
|
||||
}
|
||||
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,
|
||||
agg,
|
||||
phone.Number,
|
||||
))
|
||||
if phone.Verified {
|
||||
events = append(events, schemauser.NewPhoneVerifiedEvent(ctx, agg))
|
||||
} else {
|
||||
cryptoCode, err := c.newPhoneCode(ctx, c.eventstore.Filter, alg) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if phone.ReturnCode {
|
||||
plainCode = cryptoCode.Plain
|
||||
}
|
||||
events = append(events, schemauser.NewPhoneCodeAddedEvent(ctx, agg,
|
||||
cryptoCode.Crypted,
|
||||
cryptoCode.Expiry,
|
||||
phone.ReturnCode,
|
||||
))
|
||||
}
|
||||
return events, plainCode, nil
|
||||
}
|
||||
|
||||
func (c *Commands) getSchemaUserExists(ctx context.Context, resourceOwner, id string) (*UserV3WriteModel, error) {
|
||||
writeModel := NewExistsUserV3WriteModel(resourceOwner, id)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, writeModel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModel, nil
|
||||
}
|
174
internal/command/user_v3_model.go
Normal file
174
internal/command/user_v3_model.go
Normal file
@@ -0,0 +1,174 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/user/schemauser"
|
||||
)
|
||||
|
||||
type UserV3WriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
PhoneWM bool
|
||||
EmailWM bool
|
||||
DataWM bool
|
||||
|
||||
SchemaID string
|
||||
SchemaRevision uint64
|
||||
|
||||
Email string
|
||||
IsEmailVerified bool
|
||||
EmailVerifiedFailedCount int
|
||||
Phone string
|
||||
IsPhoneVerified bool
|
||||
PhoneVerifiedFailedCount int
|
||||
|
||||
Data json.RawMessage
|
||||
|
||||
State domain.UserState
|
||||
}
|
||||
|
||||
func NewExistsUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
PhoneWM: false,
|
||||
EmailWM: false,
|
||||
DataWM: false,
|
||||
}
|
||||
}
|
||||
|
||||
func NewUserV3WriteModel(resourceOwner, userID string) *UserV3WriteModel {
|
||||
return &UserV3WriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: userID,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
PhoneWM: true,
|
||||
EmailWM: true,
|
||||
DataWM: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *schemauser.CreatedEvent:
|
||||
wm.SchemaID = e.SchemaID
|
||||
wm.SchemaRevision = 1
|
||||
wm.Data = e.Data
|
||||
|
||||
wm.State = domain.UserStateActive
|
||||
case *schemauser.UpdatedEvent:
|
||||
if e.SchemaID != nil {
|
||||
wm.SchemaID = *e.SchemaID
|
||||
}
|
||||
if e.SchemaRevision != nil {
|
||||
wm.SchemaRevision = *e.SchemaRevision
|
||||
}
|
||||
if len(e.Data) > 0 {
|
||||
wm.Data = e.Data
|
||||
}
|
||||
case *schemauser.DeletedEvent:
|
||||
wm.State = domain.UserStateDeleted
|
||||
case *schemauser.EmailUpdatedEvent:
|
||||
wm.Email = string(e.EmailAddress)
|
||||
case *schemauser.EmailCodeAddedEvent:
|
||||
wm.IsEmailVerified = false
|
||||
wm.EmailVerifiedFailedCount = 0
|
||||
case *schemauser.EmailVerifiedEvent:
|
||||
wm.IsEmailVerified = true
|
||||
wm.EmailVerifiedFailedCount = 0
|
||||
case *schemauser.EmailVerificationFailedEvent:
|
||||
wm.EmailVerifiedFailedCount += 1
|
||||
case *schemauser.PhoneChangedEvent:
|
||||
wm.Phone = string(e.PhoneNumber)
|
||||
case *schemauser.PhoneCodeAddedEvent:
|
||||
wm.IsPhoneVerified = false
|
||||
wm.PhoneVerifiedFailedCount = 0
|
||||
case *schemauser.PhoneVerifiedEvent:
|
||||
wm.PhoneVerifiedFailedCount = 0
|
||||
wm.IsPhoneVerified = true
|
||||
case *schemauser.PhoneVerificationFailedEvent:
|
||||
wm.PhoneVerifiedFailedCount += 1
|
||||
}
|
||||
}
|
||||
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,
|
||||
)
|
||||
if wm.DataWM {
|
||||
query = query.EventTypes(
|
||||
schemauser.UpdatedType,
|
||||
)
|
||||
}
|
||||
if wm.EmailWM {
|
||||
query = query.EventTypes(
|
||||
schemauser.EmailUpdatedType,
|
||||
schemauser.EmailVerifiedType,
|
||||
schemauser.EmailCodeAddedType,
|
||||
schemauser.EmailVerificationFailedType,
|
||||
)
|
||||
}
|
||||
if wm.PhoneWM {
|
||||
query = query.EventTypes(
|
||||
schemauser.PhoneUpdatedType,
|
||||
schemauser.PhoneVerifiedType,
|
||||
schemauser.PhoneCodeAddedType,
|
||||
schemauser.PhoneVerificationFailedType,
|
||||
)
|
||||
}
|
||||
return query.Builder()
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) NewUpdatedEvent(
|
||||
ctx context.Context,
|
||||
agg *eventstore.Aggregate,
|
||||
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 schemaRevision != nil && wm.SchemaRevision != *schemaRevision {
|
||||
changes = append(changes, schemauser.ChangeSchemaRevision(wm.SchemaRevision, *schemaRevision))
|
||||
}
|
||||
if !bytes.Equal(wm.Data, data) {
|
||||
changes = append(changes, schemauser.ChangeData(data))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return schemauser.NewUpdatedEvent(ctx, agg, changes)
|
||||
}
|
||||
|
||||
func UserV3AggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||
return &eventstore.Aggregate{
|
||||
ID: wm.AggregateID,
|
||||
Type: schemauser.AggregateType,
|
||||
ResourceOwner: wm.ResourceOwner,
|
||||
InstanceID: wm.InstanceID,
|
||||
Version: schemauser.AggregateVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *UserV3WriteModel) Exists() bool {
|
||||
return wm.State != domain.UserStateDeleted && wm.State != domain.UserStateUnspecified
|
||||
}
|
1103
internal/command/user_v3_test.go
Normal file
1103
internal/command/user_v3_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,16 +20,16 @@ const (
|
||||
PermissionProperty = "urn:zitadel:schema:permission"
|
||||
)
|
||||
|
||||
type role int32
|
||||
type Role int32
|
||||
|
||||
const (
|
||||
roleUnspecified role = iota
|
||||
roleSelf
|
||||
roleOwner
|
||||
RoleUnspecified Role = iota
|
||||
RoleSelf
|
||||
RoleOwner
|
||||
)
|
||||
|
||||
type permissionExtension struct {
|
||||
role role
|
||||
role Role
|
||||
}
|
||||
|
||||
// Compile implements the [jsonschema.ExtCompiler] interface.
|
||||
@@ -57,14 +57,14 @@ func (c permissionExtension) Compile(ctx jsonschema.CompilerContext, m map[strin
|
||||
return
|
||||
}
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission role")
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission Role")
|
||||
}
|
||||
}
|
||||
return permissionExtensionConfig{c.role, perms}, nil
|
||||
}
|
||||
|
||||
type permissionExtensionConfig struct {
|
||||
role role
|
||||
role Role
|
||||
permissions *permissions
|
||||
}
|
||||
|
||||
@@ -72,17 +72,17 @@ type permissionExtensionConfig struct {
|
||||
// It validates the fields of the json instance according to the permission schema.
|
||||
func (s permissionExtensionConfig) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
|
||||
switch s.role {
|
||||
case roleSelf:
|
||||
case RoleSelf:
|
||||
if s.permissions.self == nil || !s.permissions.self.write {
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
}
|
||||
return nil
|
||||
case roleOwner:
|
||||
case RoleOwner:
|
||||
if s.permissions.owner == nil || !s.permissions.owner.write {
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
}
|
||||
return nil
|
||||
case roleUnspecified:
|
||||
case RoleUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
return ctx.Error("permission", "missing required permission")
|
||||
|
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func TestPermissionExtension(t *testing.T) {
|
||||
type args struct {
|
||||
role role
|
||||
role Role
|
||||
schema string
|
||||
instance string
|
||||
}
|
||||
@@ -83,7 +83,7 @@ func TestPermissionExtension(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid role, compilation err",
|
||||
"invalid Role, compilation err",
|
||||
args{
|
||||
schema: `{
|
||||
"type": "object",
|
||||
@@ -98,13 +98,13 @@ func TestPermissionExtension(t *testing.T) {
|
||||
}`,
|
||||
},
|
||||
want{
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission role"),
|
||||
compilationErr: zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission Role"),
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid permission self, validation err",
|
||||
args{
|
||||
role: roleSelf,
|
||||
role: RoleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -126,7 +126,7 @@ func TestPermissionExtension(t *testing.T) {
|
||||
{
|
||||
"invalid permission owner, validation err",
|
||||
args{
|
||||
role: roleOwner,
|
||||
role: RoleOwner,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -148,7 +148,7 @@ func TestPermissionExtension(t *testing.T) {
|
||||
{
|
||||
"valid permission self, ok",
|
||||
args{
|
||||
role: roleSelf,
|
||||
role: RoleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -170,7 +170,7 @@ func TestPermissionExtension(t *testing.T) {
|
||||
{
|
||||
"valid permission owner, ok",
|
||||
args{
|
||||
role: roleOwner,
|
||||
role: RoleOwner,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -190,9 +190,9 @@ func TestPermissionExtension(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"no role, validation err",
|
||||
"no Role, validation err",
|
||||
args{
|
||||
role: roleUnspecified,
|
||||
role: RoleUnspecified,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -214,7 +214,7 @@ func TestPermissionExtension(t *testing.T) {
|
||||
{
|
||||
"no permission required, ok",
|
||||
args{
|
||||
role: roleSelf,
|
||||
role: RoleSelf,
|
||||
schema: `{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@@ -19,7 +19,7 @@ const (
|
||||
MetaSchemaID = "urn:zitadel:schema:v1"
|
||||
)
|
||||
|
||||
func NewSchema(role role, r io.Reader) (*jsonschema.Schema, error) {
|
||||
func NewSchema(role Role, r io.Reader) (*jsonschema.Schema, error) {
|
||||
c := jsonschema.NewCompiler()
|
||||
if err := c.AddResource(PermissionSchemaID, strings.NewReader(permissionJSON)); err != nil {
|
||||
return nil, err
|
||||
@@ -31,11 +31,11 @@ func NewSchema(role role, r io.Reader) (*jsonschema.Schema, error) {
|
||||
role,
|
||||
})
|
||||
if err := c.AddResource("schema.json", r); err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-Frh42", "Errors.UserSchema.Schema.Invalid")
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-Frh42", "Errors.UserSchema.Invalid")
|
||||
}
|
||||
schema, err := c.Compile("schema.json")
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-W21tg", "Errors.UserSchema.Schema.Invalid")
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "COMMA-W21tg", "Errors.UserSchema.Invalid")
|
||||
}
|
||||
return schema, nil
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -378,7 +377,6 @@ func Test_CallTargets(t *testing.T) {
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
fmt.Println(respBody)
|
||||
assert.Equal(t, tt.res.ret, respBody)
|
||||
})
|
||||
}
|
||||
|
@@ -34,11 +34,14 @@ import (
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
|
||||
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
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"
|
||||
"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"
|
||||
userschema_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
webkey_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
session_v2beta "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
|
||||
@@ -46,8 +49,7 @@ import (
|
||||
settings_v2beta "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/system"
|
||||
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
user_v2beta "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
@@ -57,7 +59,7 @@ type Client struct {
|
||||
Mgmt mgmt.ManagementServiceClient
|
||||
Auth auth.AuthServiceClient
|
||||
UserV2beta user_v2beta.UserServiceClient
|
||||
UserV2 user.UserServiceClient
|
||||
UserV2 user_v2.UserServiceClient
|
||||
SessionV2beta session_v2beta.SessionServiceClient
|
||||
SessionV2 session.SessionServiceClient
|
||||
SettingsV2beta settings_v2beta.SettingsServiceClient
|
||||
@@ -70,9 +72,10 @@ type Client struct {
|
||||
ActionV3Alpha action.ZITADELActionsClient
|
||||
FeatureV2beta feature_v2beta.FeatureServiceClient
|
||||
FeatureV2 feature.FeatureServiceClient
|
||||
UserSchemaV3 schema.UserSchemaServiceClient
|
||||
UserSchemaV3 userschema_v3alpha.ZITADELUserSchemasClient
|
||||
WebKeyV3Alpha webkey_v3alpha.ZITADELWebKeysClient
|
||||
IDPv2 idp_pb.IdentityProviderServiceClient
|
||||
UserV3Alpha user_v3alpha.ZITADELUsersClient
|
||||
}
|
||||
|
||||
func newClient(cc *grpc.ClientConn) Client {
|
||||
@@ -82,7 +85,7 @@ func newClient(cc *grpc.ClientConn) Client {
|
||||
Mgmt: mgmt.NewManagementServiceClient(cc),
|
||||
Auth: auth.NewAuthServiceClient(cc),
|
||||
UserV2beta: user_v2beta.NewUserServiceClient(cc),
|
||||
UserV2: user.NewUserServiceClient(cc),
|
||||
UserV2: user_v2.NewUserServiceClient(cc),
|
||||
SessionV2beta: session_v2beta.NewSessionServiceClient(cc),
|
||||
SessionV2: session.NewSessionServiceClient(cc),
|
||||
SettingsV2beta: settings_v2beta.NewSettingsServiceClient(cc),
|
||||
@@ -95,9 +98,10 @@ func newClient(cc *grpc.ClientConn) Client {
|
||||
ActionV3Alpha: action.NewZITADELActionsClient(cc),
|
||||
FeatureV2beta: feature_v2beta.NewFeatureServiceClient(cc),
|
||||
FeatureV2: feature.NewFeatureServiceClient(cc),
|
||||
UserSchemaV3: schema.NewUserSchemaServiceClient(cc),
|
||||
UserSchemaV3: userschema_v3alpha.NewZITADELUserSchemasClient(cc),
|
||||
WebKeyV3Alpha: webkey_v3alpha.NewZITADELWebKeysClient(cc),
|
||||
IDPv2: idp_pb.NewIdentityProviderServiceClient(cc),
|
||||
UserV3Alpha: user_v3alpha.NewZITADELUsersClient(cc),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,29 +152,29 @@ func (t *Tester) UseIsolatedInstance(tt *testing.T, iamOwnerCtx, systemCtx conte
|
||||
return primaryDomain, instanceId, adminUser.GetUserId(), t.updateInstanceAndOrg(newCtx, fmt.Sprintf("%s:%d", primaryDomain, t.Config.ExternalPort))
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUser(ctx context.Context) *user.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user.AddHumanUserRequest{
|
||||
func (s *Tester) CreateHumanUser(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
},
|
||||
},
|
||||
Profile: &user.SetHumanProfile{
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "Mickey",
|
||||
FamilyName: "Mouse",
|
||||
PreferredLanguage: gu.Ptr("nl"),
|
||||
Gender: gu.Ptr(user.Gender_GENDER_MALE),
|
||||
Gender: gu.Ptr(user_v2.Gender_GENDER_MALE),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
Verification: &user.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||
Verification: &user_v2.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: &user_v2.SetHumanPhone{
|
||||
Phone: "+41791234567",
|
||||
Verification: &user.SetHumanPhone_ReturnCode{
|
||||
ReturnCode: &user.ReturnPhoneVerificationCode{},
|
||||
Verification: &user_v2.SetHumanPhone_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnPhoneVerificationCode{},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -178,23 +182,23 @@ func (s *Tester) CreateHumanUser(ctx context.Context) *user.AddHumanUserResponse
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserNoPhone(ctx context.Context) *user.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user.AddHumanUserRequest{
|
||||
func (s *Tester) CreateHumanUserNoPhone(ctx context.Context) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
},
|
||||
},
|
||||
Profile: &user.SetHumanProfile{
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "Mickey",
|
||||
FamilyName: "Mouse",
|
||||
PreferredLanguage: gu.Ptr("nl"),
|
||||
Gender: gu.Ptr(user.Gender_GENDER_MALE),
|
||||
Gender: gu.Ptr(user_v2.Gender_GENDER_MALE),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
Verification: &user.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||
Verification: &user_v2.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -202,29 +206,29 @@ func (s *Tester) CreateHumanUserNoPhone(ctx context.Context) *user.AddHumanUserR
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserWithTOTP(ctx context.Context, secret string) *user.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user.AddHumanUserRequest{
|
||||
func (s *Tester) CreateHumanUserWithTOTP(ctx context.Context, secret string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: s.Organisation.ID,
|
||||
},
|
||||
},
|
||||
Profile: &user.SetHumanProfile{
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "Mickey",
|
||||
FamilyName: "Mouse",
|
||||
PreferredLanguage: gu.Ptr("nl"),
|
||||
Gender: gu.Ptr(user.Gender_GENDER_MALE),
|
||||
Gender: gu.Ptr(user_v2.Gender_GENDER_MALE),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()),
|
||||
Verification: &user.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||
Verification: &user_v2.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: &user_v2.SetHumanPhone{
|
||||
Phone: "+41791234567",
|
||||
Verification: &user.SetHumanPhone_ReturnCode{
|
||||
ReturnCode: &user.ReturnPhoneVerificationCode{},
|
||||
Verification: &user_v2.SetHumanPhone_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnPhoneVerificationCode{},
|
||||
},
|
||||
},
|
||||
TotpSecret: gu.Ptr(secret),
|
||||
@@ -239,15 +243,15 @@ func (s *Tester) CreateOrganization(ctx context.Context, name, adminEmail string
|
||||
Admins: []*org.AddOrganizationRequest_Admin{
|
||||
{
|
||||
UserType: &org.AddOrganizationRequest_Admin_Human{
|
||||
Human: &user.AddHumanUserRequest{
|
||||
Profile: &user.SetHumanProfile{
|
||||
Human: &user_v2.AddHumanUserRequest{
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "firstname",
|
||||
FamilyName: "lastname",
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: adminEmail,
|
||||
Verification: &user.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||
Verification: &user_v2.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user_v2.ReturnEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -292,29 +296,29 @@ func (s *Tester) CreateOrganizationWithUserID(ctx context.Context, name, userID
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateHumanUserVerified(ctx context.Context, org, email string) *user.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user.AddHumanUserRequest{
|
||||
func (s *Tester) CreateHumanUserVerified(ctx context.Context, org, email string) *user_v2.AddHumanUserResponse {
|
||||
resp, err := s.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||
Organization: &object.Organization{
|
||||
Org: &object.Organization_OrgId{
|
||||
OrgId: org,
|
||||
},
|
||||
},
|
||||
Profile: &user.SetHumanProfile{
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "Mickey",
|
||||
FamilyName: "Mouse",
|
||||
NickName: gu.Ptr("Mickey"),
|
||||
PreferredLanguage: gu.Ptr("nl"),
|
||||
Gender: gu.Ptr(user.Gender_GENDER_MALE),
|
||||
Gender: gu.Ptr(user_v2.Gender_GENDER_MALE),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: &user_v2.SetHumanEmail{
|
||||
Email: email,
|
||||
Verification: &user.SetHumanEmail_IsVerified{
|
||||
Verification: &user_v2.SetHumanEmail_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: &user_v2.SetHumanPhone{
|
||||
Phone: "+41791234567",
|
||||
Verification: &user.SetHumanPhone_IsVerified{
|
||||
Verification: &user_v2.SetHumanPhone_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
@@ -334,12 +338,12 @@ func (s *Tester) CreateMachineUser(ctx context.Context) *mgmt.AddMachineUserResp
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpID, username string) *user.AddIDPLinkResponse {
|
||||
func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpID, username string) *user_v2.AddIDPLinkResponse {
|
||||
resp, err := s.Client.UserV2.AddIDPLink(
|
||||
ctx,
|
||||
&user.AddIDPLinkRequest{
|
||||
&user_v2.AddIDPLinkRequest{
|
||||
UserId: userID,
|
||||
IdpLink: &user.IDPLink{
|
||||
IdpLink: &user_v2.IDPLink{
|
||||
IdpId: idpID,
|
||||
UserId: externalID,
|
||||
UserName: username,
|
||||
@@ -351,13 +355,13 @@ func (s *Tester) CreateUserIDPlink(ctx context.Context, userID, externalID, idpI
|
||||
}
|
||||
|
||||
func (s *Tester) RegisterUserPasskey(ctx context.Context, userID string) {
|
||||
reg, err := s.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user.CreatePasskeyRegistrationLinkRequest{
|
||||
reg, err := s.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user_v2.CreatePasskeyRegistrationLinkRequest{
|
||||
UserId: userID,
|
||||
Medium: &user.CreatePasskeyRegistrationLinkRequest_ReturnCode{},
|
||||
Medium: &user_v2.CreatePasskeyRegistrationLinkRequest_ReturnCode{},
|
||||
})
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
|
||||
pkr, err := s.Client.UserV2.RegisterPasskey(ctx, &user.RegisterPasskeyRequest{
|
||||
pkr, err := s.Client.UserV2.RegisterPasskey(ctx, &user_v2.RegisterPasskeyRequest{
|
||||
UserId: userID,
|
||||
Code: reg.GetCode(),
|
||||
Domain: s.Config.ExternalDomain,
|
||||
@@ -366,7 +370,7 @@ func (s *Tester) RegisterUserPasskey(ctx context.Context, userID string) {
|
||||
attestationResponse, err := s.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Fatal("create user passkey")
|
||||
|
||||
_, err = s.Client.UserV2.VerifyPasskeyRegistration(ctx, &user.VerifyPasskeyRegistrationRequest{
|
||||
_, err = s.Client.UserV2.VerifyPasskeyRegistration(ctx, &user_v2.VerifyPasskeyRegistrationRequest{
|
||||
UserId: userID,
|
||||
PasskeyId: pkr.GetPasskeyId(),
|
||||
PublicKeyCredential: attestationResponse,
|
||||
@@ -376,7 +380,7 @@ func (s *Tester) RegisterUserPasskey(ctx context.Context, userID string) {
|
||||
}
|
||||
|
||||
func (s *Tester) RegisterUserU2F(ctx context.Context, userID string) {
|
||||
pkr, err := s.Client.UserV2.RegisterU2F(ctx, &user.RegisterU2FRequest{
|
||||
pkr, err := s.Client.UserV2.RegisterU2F(ctx, &user_v2.RegisterU2FRequest{
|
||||
UserId: userID,
|
||||
Domain: s.Config.ExternalDomain,
|
||||
})
|
||||
@@ -384,7 +388,7 @@ func (s *Tester) RegisterUserU2F(ctx context.Context, userID string) {
|
||||
attestationResponse, err := s.WebAuthN.CreateAttestationResponse(pkr.GetPublicKeyCredentialCreationOptions())
|
||||
logging.OnError(err).Fatal("create user u2f")
|
||||
|
||||
_, err = s.Client.UserV2.VerifyU2FRegistration(ctx, &user.VerifyU2FRegistrationRequest{
|
||||
_, err = s.Client.UserV2.VerifyU2FRegistration(ctx, &user_v2.VerifyU2FRegistrationRequest{
|
||||
UserId: userID,
|
||||
U2FId: pkr.GetU2FId(),
|
||||
PublicKeyCredential: attestationResponse,
|
||||
@@ -394,9 +398,9 @@ func (s *Tester) RegisterUserU2F(ctx context.Context, userID string) {
|
||||
}
|
||||
|
||||
func (s *Tester) SetUserPassword(ctx context.Context, userID, password string, changeRequired bool) *object.Details {
|
||||
resp, err := s.Client.UserV2.SetPassword(ctx, &user.SetPasswordRequest{
|
||||
resp, err := s.Client.UserV2.SetPassword(ctx, &user_v2.SetPasswordRequest{
|
||||
UserId: userID,
|
||||
NewPassword: &user.Password{
|
||||
NewPassword: &user_v2.Password{
|
||||
Password: password,
|
||||
ChangeRequired: changeRequired,
|
||||
},
|
||||
@@ -757,24 +761,57 @@ func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *action.Co
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchema(ctx context.Context, t *testing.T) *schema.CreateUserSchemaResponse {
|
||||
return s.CreateUserSchemaWithType(ctx, t, fmt.Sprint(time.Now().UnixNano()+1))
|
||||
func (s *Tester) CreateUserSchemaEmpty(ctx context.Context) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
return s.CreateUserSchemaEmptyWithType(ctx, fmt.Sprint(time.Now().UnixNano()+1))
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchemaWithType(ctx context.Context, t *testing.T, schemaType string) *schema.CreateUserSchemaResponse {
|
||||
func (s *Tester) CreateUserSchema(ctx context.Context, schemaData []byte) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON(schemaData)
|
||||
logging.OnError(err).Fatal("create userschema unmarshal")
|
||||
schema, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
UserSchema: &userschema_v3alpha.CreateUserSchema{
|
||||
Type: fmt.Sprint(time.Now().UnixNano() + 1),
|
||||
DataType: &userschema_v3alpha.CreateUserSchema_Schema{
|
||||
Schema: userSchema,
|
||||
},
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create userschema")
|
||||
return schema
|
||||
}
|
||||
|
||||
func (s *Tester) CreateUserSchemaEmptyWithType(ctx context.Context, schemaType string) *userschema_v3alpha.CreateUserSchemaResponse {
|
||||
userSchema := new(structpb.Struct)
|
||||
err := userSchema.UnmarshalJSON([]byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}`))
|
||||
require.NoError(t, err)
|
||||
target, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &schema.CreateUserSchemaRequest{
|
||||
Type: schemaType,
|
||||
DataType: &schema.CreateUserSchemaRequest_Schema{
|
||||
Schema: userSchema,
|
||||
logging.OnError(err).Fatal("create userschema unmarshal")
|
||||
schema, err := s.Client.UserSchemaV3.CreateUserSchema(ctx, &userschema_v3alpha.CreateUserSchemaRequest{
|
||||
UserSchema: &userschema_v3alpha.CreateUserSchema{
|
||||
Type: schemaType,
|
||||
DataType: &userschema_v3alpha.CreateUserSchema_Schema{
|
||||
Schema: userSchema,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
logging.OnError(err).Fatal("create userschema")
|
||||
return schema
|
||||
}
|
||||
|
||||
func (s *Tester) CreateSchemaUser(ctx context.Context, orgID string, schemaID string, data []byte) *user_v3alpha.CreateUserResponse {
|
||||
userData := new(structpb.Struct)
|
||||
err := userData.UnmarshalJSON(data)
|
||||
logging.OnError(err).Fatal("create user unmarshal")
|
||||
user, err := s.Client.UserV3Alpha.CreateUser(ctx, &user_v3alpha.CreateUserRequest{
|
||||
Organization: &object_v3alpha.Organization{Property: &object_v3alpha.Organization_OrgId{OrgId: orgID}},
|
||||
User: &user_v3alpha.CreateUser{
|
||||
SchemaId: schemaID,
|
||||
Data: userData,
|
||||
},
|
||||
})
|
||||
logging.OnError(err).Fatal("create user")
|
||||
return user
|
||||
}
|
||||
|
@@ -12,9 +12,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
UserSchemaTable = "projections.user_schemas"
|
||||
UserSchemaTable = "projections.user_schemas1"
|
||||
|
||||
UserSchemaIDCol = "id"
|
||||
UserSchemaCreationDateCol = "creation_date"
|
||||
UserSchemaChangeDateCol = "change_date"
|
||||
UserSchemaSequenceCol = "sequence"
|
||||
UserSchemaInstanceIDCol = "instance_id"
|
||||
@@ -39,6 +40,7 @@ func (*userSchemaProjection) Init() *old_handler.Check {
|
||||
return handler.NewTableCheck(
|
||||
handler.NewTable([]*handler.InitColumn{
|
||||
handler.NewColumn(UserSchemaIDCol, handler.ColumnTypeText),
|
||||
handler.NewColumn(UserSchemaCreationDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(UserSchemaChangeDateCol, handler.ColumnTypeTimestamp),
|
||||
handler.NewColumn(UserSchemaSequenceCol, handler.ColumnTypeInt64),
|
||||
handler.NewColumn(UserSchemaStateCol, handler.ColumnTypeEnum),
|
||||
@@ -102,6 +104,7 @@ func (p *userSchemaProjection) reduceCreated(event eventstore.Event) (*handler.S
|
||||
event,
|
||||
[]handler.Column{
|
||||
handler.NewCol(UserSchemaIDCol, event.Aggregate().ID),
|
||||
handler.NewCol(UserSchemaCreationDateCol, handler.OnlySetValueOnInsert(UserSchemaTable, e.CreationDate())),
|
||||
handler.NewCol(UserSchemaChangeDateCol, event.CreatedAt()),
|
||||
handler.NewCol(UserSchemaSequenceCol, event.Sequence()),
|
||||
handler.NewCol(UserSchemaInstanceIDCol, event.Aggregate().InstanceID),
|
||||
@@ -130,7 +133,10 @@ func (p *userSchemaProjection) reduceUpdated(event eventstore.Event) (*handler.S
|
||||
|
||||
if len(e.Schema) > 0 {
|
||||
cols = append(cols, handler.NewCol(UserSchemaSchemaCol, e.Schema))
|
||||
cols = append(cols, handler.NewIncrementCol(UserSchemaRevisionCol, 1))
|
||||
}
|
||||
|
||||
if e.SchemaRevision != nil {
|
||||
cols = append(cols, handler.NewCol(UserSchemaRevisionCol, *e.SchemaRevision))
|
||||
}
|
||||
|
||||
if len(e.PossibleAuthenticators) > 0 {
|
||||
|
@@ -39,10 +39,11 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.user_schemas (id, change_date, sequence, instance_id, state, type, revision, schema, possible_authenticators) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedStmt: "INSERT INTO projections.user_schemas1 (id, creation_date, change_date, sequence, instance_id, state, type, revision, schema, possible_authenticators) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"instance-id",
|
||||
domain.UserSchemaStateActive,
|
||||
@@ -61,9 +62,9 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
schema.CreatedType,
|
||||
schema.UpdatedType,
|
||||
schema.AggregateType,
|
||||
[]byte(`{"schemaType": "type", "schema": {"$schema":"urn:zitadel:schema:v1","properties":{"name":{"type":"string","urn:zitadel:schema:permission":{"self":"rw"}}},"type":"object"}, "possibleAuthenticators": [1,2]}`),
|
||||
[]byte(`{"schemaType": "type", "schemaRevision": 2, "schema": {"$schema":"urn:zitadel:schema:v1","properties":{"name":{"type":"string","urn:zitadel:schema:permission":{"self":"rw"}}},"type":"object"}, "possibleAuthenticators": [1,2]}`),
|
||||
), eventstore.GenericEventMapper[schema.UpdatedEvent]),
|
||||
},
|
||||
reduce: (&userSchemaProjection{}).reduceUpdated,
|
||||
@@ -73,13 +74,13 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.user_schemas SET (change_date, sequence, type, schema, revision, possible_authenticators) = ($1, $2, $3, $4, revision + $5, $6) WHERE (id = $7) AND (instance_id = $8)",
|
||||
expectedStmt: "UPDATE projections.user_schemas1 SET (change_date, sequence, type, schema, revision, possible_authenticators) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"type",
|
||||
json.RawMessage(`{"$schema":"urn:zitadel:schema:v1","properties":{"name":{"type":"string","urn:zitadel:schema:permission":{"self":"rw"}}},"type":"object"}`),
|
||||
1,
|
||||
uint64(2),
|
||||
[]domain.AuthenticatorType{domain.AuthenticatorTypeUsername, domain.AuthenticatorTypePassword},
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@@ -106,7 +107,7 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.user_schemas SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.user_schemas1 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@@ -136,7 +137,7 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.user_schemas SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.user_schemas1 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@@ -166,7 +167,7 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.user_schemas WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.user_schemas1 WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@@ -193,7 +194,7 @@ func TestUserSchemaProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.user_schemas WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.user_schemas1 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@@ -26,7 +26,6 @@ func (e *UserSchemas) SetState(s *State) {
|
||||
}
|
||||
|
||||
type UserSchema struct {
|
||||
ID string
|
||||
domain.ObjectDetails
|
||||
State domain.UserSchemaState
|
||||
Type string
|
||||
@@ -49,6 +48,10 @@ var (
|
||||
name: projection.UserSchemaIDCol,
|
||||
table: userSchemaTable,
|
||||
}
|
||||
UserSchemaCreationDateCol = Column{
|
||||
name: projection.UserSchemaCreationDateCol,
|
||||
table: userSchemaTable,
|
||||
}
|
||||
UserSchemaChangeDateCol = Column{
|
||||
name: projection.UserSchemaChangeDateCol,
|
||||
table: userSchemaTable,
|
||||
@@ -131,6 +134,7 @@ func NewUserSchemaStateSearchQuery(value domain.UserSchemaState) (SearchQuery, e
|
||||
func prepareUserSchemaQuery() (sq.SelectBuilder, func(*sql.Row) (*UserSchema, error)) {
|
||||
return sq.Select(
|
||||
UserSchemaIDCol.identifier(),
|
||||
UserSchemaCreationDateCol.identifier(),
|
||||
UserSchemaChangeDateCol.identifier(),
|
||||
UserSchemaSequenceCol.identifier(),
|
||||
UserSchemaInstanceIDCol.identifier(),
|
||||
@@ -147,6 +151,7 @@ func prepareUserSchemaQuery() (sq.SelectBuilder, func(*sql.Row) (*UserSchema, er
|
||||
var schema database.ByteArray[byte]
|
||||
err := row.Scan(
|
||||
&u.ID,
|
||||
&u.CreationDate,
|
||||
&u.EventDate,
|
||||
&u.Sequence,
|
||||
&u.ResourceOwner,
|
||||
@@ -173,6 +178,7 @@ func prepareUserSchemaQuery() (sq.SelectBuilder, func(*sql.Row) (*UserSchema, er
|
||||
func prepareUserSchemasQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserSchemas, error)) {
|
||||
return sq.Select(
|
||||
UserSchemaIDCol.identifier(),
|
||||
UserSchemaCreationDateCol.identifier(),
|
||||
UserSchemaChangeDateCol.identifier(),
|
||||
UserSchemaSequenceCol.identifier(),
|
||||
UserSchemaInstanceIDCol.identifier(),
|
||||
@@ -195,6 +201,7 @@ func prepareUserSchemasQuery() (sq.SelectBuilder, func(*sql.Rows) (*UserSchemas,
|
||||
u := new(UserSchema)
|
||||
err := rows.Scan(
|
||||
&u.ID,
|
||||
&u.CreationDate,
|
||||
&u.EventDate,
|
||||
&u.Sequence,
|
||||
&u.ResourceOwner,
|
||||
|
@@ -15,19 +15,21 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
prepareUserSchemasStmt = `SELECT projections.user_schemas.id,` +
|
||||
` projections.user_schemas.change_date,` +
|
||||
` projections.user_schemas.sequence,` +
|
||||
` projections.user_schemas.instance_id,` +
|
||||
` projections.user_schemas.state,` +
|
||||
` projections.user_schemas.type,` +
|
||||
` projections.user_schemas.revision,` +
|
||||
` projections.user_schemas.schema,` +
|
||||
` projections.user_schemas.possible_authenticators,` +
|
||||
prepareUserSchemasStmt = `SELECT projections.user_schemas1.id,` +
|
||||
` projections.user_schemas1.creation_date,` +
|
||||
` projections.user_schemas1.change_date,` +
|
||||
` projections.user_schemas1.sequence,` +
|
||||
` projections.user_schemas1.instance_id,` +
|
||||
` projections.user_schemas1.state,` +
|
||||
` projections.user_schemas1.type,` +
|
||||
` projections.user_schemas1.revision,` +
|
||||
` projections.user_schemas1.schema,` +
|
||||
` projections.user_schemas1.possible_authenticators,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.user_schemas`
|
||||
prepareUserSchemasCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"sequence",
|
||||
"instance_id",
|
||||
@@ -39,18 +41,20 @@ var (
|
||||
"count",
|
||||
}
|
||||
|
||||
prepareUserSchemaStmt = `SELECT projections.user_schemas.id,` +
|
||||
` projections.user_schemas.change_date,` +
|
||||
` projections.user_schemas.sequence,` +
|
||||
` projections.user_schemas.instance_id,` +
|
||||
` projections.user_schemas.state,` +
|
||||
` projections.user_schemas.type,` +
|
||||
` projections.user_schemas.revision,` +
|
||||
` projections.user_schemas.schema,` +
|
||||
` projections.user_schemas.possible_authenticators` +
|
||||
prepareUserSchemaStmt = `SELECT projections.user_schemas1.id,` +
|
||||
` projections.user_schemas1.creation_date,` +
|
||||
` projections.user_schemas1.change_date,` +
|
||||
` projections.user_schemas1.sequence,` +
|
||||
` projections.user_schemas1.instance_id,` +
|
||||
` projections.user_schemas1.state,` +
|
||||
` projections.user_schemas1.type,` +
|
||||
` projections.user_schemas1.revision,` +
|
||||
` projections.user_schemas1.schema,` +
|
||||
` projections.user_schemas1.possible_authenticators` +
|
||||
` FROM projections.user_schemas`
|
||||
prepareUserSchemaCols = []string{
|
||||
"id",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"sequence",
|
||||
"instance_id",
|
||||
@@ -96,6 +100,7 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
"instance-id",
|
||||
domain.UserSchemaStateActive,
|
||||
@@ -113,9 +118,10 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
},
|
||||
UserSchemas: []*UserSchema{
|
||||
{
|
||||
ID: "id",
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
ID: "id",
|
||||
EventDate: testNow,
|
||||
CreationDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "instance-id",
|
||||
},
|
||||
@@ -139,6 +145,7 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
{
|
||||
"id-1",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
"instance-id",
|
||||
domain.UserSchemaStateActive,
|
||||
@@ -150,6 +157,7 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
{
|
||||
"id-2",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211110),
|
||||
"instance-id",
|
||||
domain.UserSchemaStateInactive,
|
||||
@@ -167,9 +175,10 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
},
|
||||
UserSchemas: []*UserSchema{
|
||||
{
|
||||
ID: "id-1",
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
ID: "id-1",
|
||||
EventDate: testNow,
|
||||
CreationDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "instance-id",
|
||||
},
|
||||
@@ -180,9 +189,10 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
PossibleAuthenticators: database.NumberArray[domain.AuthenticatorType]{domain.AuthenticatorTypeUsername, domain.AuthenticatorTypePassword},
|
||||
},
|
||||
{
|
||||
ID: "id-2",
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
ID: "id-2",
|
||||
EventDate: testNow,
|
||||
CreationDate: testNow,
|
||||
Sequence: 20211110,
|
||||
ResourceOwner: "instance-id",
|
||||
},
|
||||
@@ -240,6 +250,7 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
[]driver.Value{
|
||||
"id",
|
||||
testNow,
|
||||
testNow,
|
||||
uint64(20211109),
|
||||
"instance-id",
|
||||
domain.UserSchemaStateActive,
|
||||
@@ -251,9 +262,10 @@ func Test_UserSchemaPrepares(t *testing.T) {
|
||||
),
|
||||
},
|
||||
object: &UserSchema{
|
||||
ID: "id",
|
||||
ObjectDetails: domain.ObjectDetails{
|
||||
ID: "id",
|
||||
EventDate: testNow,
|
||||
CreationDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "instance-id",
|
||||
},
|
||||
|
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
@@ -79,7 +81,9 @@ type UpdatedEvent struct {
|
||||
SchemaType *string `json:"schemaType,omitempty"`
|
||||
Schema json.RawMessage `json:"schema,omitempty"`
|
||||
PossibleAuthenticators []domain.AuthenticatorType `json:"possibleAuthenticators,omitempty"`
|
||||
SchemaRevision *uint64 `json:"schemaRevision,omitempty"`
|
||||
oldSchemaType string
|
||||
oldRevision uint64
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
@@ -139,6 +143,13 @@ func ChangePossibleAuthenticators(possibleAuthenticators []domain.AuthenticatorT
|
||||
}
|
||||
}
|
||||
|
||||
func IncreaseRevision(oldRevision uint64) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaRevision = gu.Ptr(oldRevision + 1)
|
||||
e.oldRevision = oldRevision
|
||||
}
|
||||
}
|
||||
|
||||
type DeactivatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
25
internal/repository/user/schemauser/aggregate.go
Normal file
25
internal/repository/user/schemauser/aggregate.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package schemauser
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
AggregateType = "user"
|
||||
AggregateVersion = "v3"
|
||||
)
|
||||
|
||||
type Aggregate struct {
|
||||
eventstore.Aggregate
|
||||
}
|
||||
|
||||
func NewAggregate(id, resourceOwner string) *Aggregate {
|
||||
return &Aggregate{
|
||||
Aggregate: eventstore.Aggregate{
|
||||
Type: AggregateType,
|
||||
Version: AggregateVersion,
|
||||
ID: id,
|
||||
ResourceOwner: resourceOwner,
|
||||
},
|
||||
}
|
||||
}
|
202
internal/repository/user/schemauser/email.go
Normal file
202
internal/repository/user/schemauser/email.go
Normal file
@@ -0,0 +1,202 @@
|
||||
package schemauser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"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 (
|
||||
emailEventPrefix = eventPrefix + "email."
|
||||
EmailUpdatedType = emailEventPrefix + "updated"
|
||||
EmailVerifiedType = emailEventPrefix + "verified"
|
||||
EmailVerificationFailedType = emailEventPrefix + "verification.failed"
|
||||
EmailCodeAddedType = emailEventPrefix + "code.added"
|
||||
EmailCodeSentType = emailEventPrefix + "code.sent"
|
||||
)
|
||||
|
||||
type EmailUpdatedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
EmailAddress domain.EmailAddress `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmailUpdatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *EmailUpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewEmailUpdatedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress domain.EmailAddress) *EmailUpdatedEvent {
|
||||
return &EmailUpdatedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailUpdatedType,
|
||||
),
|
||||
EmailAddress: emailAddress,
|
||||
}
|
||||
}
|
||||
|
||||
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:"-"`
|
||||
|
||||
IsEmailVerified bool `json:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailVerifiedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmailVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewEmailVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerifiedEvent {
|
||||
return &EmailVerifiedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailVerifiedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailVerificationFailedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmailVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanEmailVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailVerificationFailedEvent {
|
||||
return &EmailVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailVerificationFailedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func EmailVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &EmailVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type EmailCodeAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
URLTemplate string `json:"url_template,omitempty"`
|
||||
CodeReturned bool `json:"code_returned,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *EmailCodeAddedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *EmailCodeAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmailCodeAddedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func NewEmailCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
urlTemplate string,
|
||||
codeReturned bool,
|
||||
) *EmailCodeAddedEvent {
|
||||
return &EmailCodeAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailCodeAddedType,
|
||||
),
|
||||
Code: code,
|
||||
Expiry: expiry,
|
||||
URLTemplate: urlTemplate,
|
||||
CodeReturned: codeReturned,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
func (e *EmailCodeSentEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmailCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanEmailCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *EmailCodeSentEvent {
|
||||
return &EmailCodeSentEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
EmailCodeSentType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func EmailCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &EmailCodeSentEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
9
internal/repository/user/schemauser/eventstore.go
Normal file
9
internal/repository/user/schemauser/eventstore.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package schemauser
|
||||
|
||||
import "github.com/zitadel/zitadel/internal/eventstore"
|
||||
|
||||
func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, CreatedType, eventstore.GenericEventMapper[CreatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, UpdatedType, eventstore.GenericEventMapper[UpdatedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, DeletedType, eventstore.GenericEventMapper[DeletedEvent])
|
||||
}
|
198
internal/repository/user/schemauser/phone.go
Normal file
198
internal/repository/user/schemauser/phone.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package schemauser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/http"
|
||||
"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 (
|
||||
phoneEventPrefix = eventPrefix + "phone."
|
||||
PhoneUpdatedType = phoneEventPrefix + "updated"
|
||||
PhoneVerifiedType = phoneEventPrefix + "verified"
|
||||
PhoneVerificationFailedType = phoneEventPrefix + "verification.failed"
|
||||
PhoneCodeAddedType = phoneEventPrefix + "code.added"
|
||||
PhoneCodeSentType = phoneEventPrefix + "code.sent"
|
||||
)
|
||||
|
||||
type PhoneChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PhoneChangedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PhoneChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *PhoneChangedEvent {
|
||||
return &PhoneChangedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneUpdatedType,
|
||||
),
|
||||
PhoneNumber: phone,
|
||||
}
|
||||
}
|
||||
|
||||
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:"-"`
|
||||
|
||||
IsPhoneVerified bool `json:"-"`
|
||||
}
|
||||
|
||||
func (e *PhoneVerifiedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PhoneVerifiedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPhoneVerifiedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerifiedEvent {
|
||||
return &PhoneVerifiedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneVerifiedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneVerifiedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneVerifiedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
IsPhoneVerified: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PhoneVerificationFailedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *PhoneVerificationFailedEvent) Payload() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PhoneVerificationFailedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPhoneVerificationFailedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneVerificationFailedEvent {
|
||||
return &PhoneVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneVerificationFailedType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneVerificationFailedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneVerificationFailedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type PhoneCodeAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||
Expiry time.Duration `json:"expiry,omitempty"`
|
||||
CodeReturned bool `json:"code_returned,omitempty"`
|
||||
TriggeredAtOrigin string `json:"triggerOrigin,omitempty"`
|
||||
}
|
||||
|
||||
func (e *PhoneCodeAddedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PhoneCodeAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *PhoneCodeAddedEvent) TriggerOrigin() string {
|
||||
return e.TriggeredAtOrigin
|
||||
}
|
||||
|
||||
func NewPhoneCodeAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
code *crypto.CryptoValue,
|
||||
expiry time.Duration,
|
||||
codeReturned bool,
|
||||
) *PhoneCodeAddedEvent {
|
||||
return &PhoneCodeAddedEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneCodeAddedType,
|
||||
),
|
||||
Code: code,
|
||||
Expiry: expiry,
|
||||
CodeReturned: codeReturned,
|
||||
TriggeredAtOrigin: http.DomainContext(ctx).Origin(),
|
||||
}
|
||||
}
|
||||
|
||||
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:"-"`
|
||||
}
|
||||
|
||||
func (e *PhoneCodeSentEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *PhoneCodeSentEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPhoneCodeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *PhoneCodeSentEvent {
|
||||
return &PhoneCodeSentEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
PhoneCodeSentType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func PhoneCodeSentEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
return &PhoneCodeSentEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
144
internal/repository/user/schemauser/user.go
Normal file
144
internal/repository/user/schemauser/user.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package schemauser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
const (
|
||||
eventPrefix = "user."
|
||||
CreatedType = eventPrefix + "created"
|
||||
UpdatedType = eventPrefix + "updated"
|
||||
DeletedType = eventPrefix + "deleted"
|
||||
)
|
||||
|
||||
type CreatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
ID string `json:"id"`
|
||||
SchemaID string `json:"schemaID"`
|
||||
SchemaRevision uint64 `json:"schemaRevision"`
|
||||
Data json.RawMessage `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *CreatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewCreatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
|
||||
schemaID string,
|
||||
schemaRevision uint64,
|
||||
data json.RawMessage,
|
||||
) *CreatedEvent {
|
||||
return &CreatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
CreatedType,
|
||||
),
|
||||
SchemaID: schemaID,
|
||||
SchemaRevision: schemaRevision,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type UpdatedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
|
||||
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) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *UpdatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
func NewUpdatedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []Changes,
|
||||
) *UpdatedEvent {
|
||||
updatedEvent := &UpdatedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
UpdatedType,
|
||||
),
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(updatedEvent)
|
||||
}
|
||||
return updatedEvent
|
||||
}
|
||||
|
||||
type Changes func(event *UpdatedEvent)
|
||||
|
||||
func ChangeSchemaID(oldSchemaID, schemaID string) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaID = &schemaID
|
||||
e.oldSchemaID = oldSchemaID
|
||||
}
|
||||
}
|
||||
func ChangeSchemaRevision(oldSchemaRevision, schemaRevision uint64) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.SchemaRevision = &schemaRevision
|
||||
e.oldRevision = oldSchemaRevision
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeData(data json.RawMessage) func(event *UpdatedEvent) {
|
||||
return func(e *UpdatedEvent) {
|
||||
e.Data = data
|
||||
}
|
||||
}
|
||||
|
||||
type DeletedEvent struct {
|
||||
*eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = event
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) Payload() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *DeletedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDeletedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *DeletedEvent {
|
||||
return &DeletedEvent{
|
||||
BaseEvent: eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
DeletedType,
|
||||
),
|
||||
}
|
||||
}
|
@@ -593,6 +593,11 @@ Errors:
|
||||
NotActive: Потребителската схема не е активна
|
||||
NotInactive: Потребителската схема не е неактивна
|
||||
NotExists: Потребителската схема не съществува
|
||||
ID:
|
||||
Missing: Липсва идентификатор на потребителска схема
|
||||
Invalid: Потребителската схема е невалидна
|
||||
Data:
|
||||
Invalid: Невалидни данни за потребителска схема
|
||||
TokenExchange:
|
||||
FeatureDisabled: Функцията Token Exchange е деактивирана за вашето копие. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1351,6 +1356,26 @@ EventTypes:
|
||||
deactivated: Потребителската схема е деактивирана
|
||||
reactivated: Потребителската схема е активирана отново
|
||||
deleted: Потребителската схема е изтрита
|
||||
user:
|
||||
created: Потребител е създаден
|
||||
updated: Потребителят е актуализиран
|
||||
deleted: Потребителят е изтрит
|
||||
email:
|
||||
updated: Имейл адресът е променен
|
||||
verified: Имейл адресът е потвърден
|
||||
verification:
|
||||
failed: Проверката на имейл адреса не бе успешна
|
||||
code:
|
||||
added: Генериран код за потвърждение на имейл адрес
|
||||
sent: Кодът за потвърждение на имейл адреса е изпратен
|
||||
phone:
|
||||
updated: Телефонният номер е променен
|
||||
verified: Телефонният номер е потвърден
|
||||
verification:
|
||||
failed: Неуспешна проверка на телефонния номер
|
||||
code:
|
||||
added: Генериран код за потвърждение на телефонен номер
|
||||
sent: Кодът за потвърждение на телефонния номер е изпратен
|
||||
web_key:
|
||||
added: Добавен уеб ключ
|
||||
activated: Уеб ключът е активиран
|
||||
|
@@ -574,6 +574,11 @@ Errors:
|
||||
NotActive: Uživatelské schéma není aktivní
|
||||
NotInactive: Uživatelské schéma není neaktivní
|
||||
NotExists: Uživatelské schéma neexistuje
|
||||
ID:
|
||||
Missing: Chybí ID schématu uživatele
|
||||
Invalid: Uživatelské schéma je neplatné
|
||||
Data:
|
||||
Invalid: Data neplatná pro uživatelské schéma
|
||||
TokenExchange:
|
||||
FeatureDisabled: Funkce Token Exchange je pro vaši instanci zakázána. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1317,6 +1322,26 @@ EventTypes:
|
||||
deactivated: Uživatelské schéma deaktivováno
|
||||
reactivated: Uživatelské schéma bylo znovu aktivováno
|
||||
deleted: Uživatelské schéma bylo smazáno
|
||||
user:
|
||||
created: Uživatel vytvořen
|
||||
updated: Uživatel aktualizován
|
||||
deleted: Uživatel byl smazán
|
||||
email:
|
||||
updated: E-mailová adresa změněna
|
||||
verified: E-mailová adresa ověřena
|
||||
verification:
|
||||
failed: Ověření e-mailové adresy se nezdařilo
|
||||
code:
|
||||
added: Vygenerován ověřovací kód e-mailové adresy
|
||||
sent: Ověřovací kód e-mailové adresy odeslán
|
||||
phone:
|
||||
updated: Telefonní číslo změněno
|
||||
verified: Telefonní číslo ověřeno
|
||||
verification:
|
||||
failed: Ověření telefonního čísla se nezdařilo
|
||||
code:
|
||||
added: Byl vygenerován ověřovací kód telefonního čísla
|
||||
sent: Ověřovací kód telefonního čísla odeslán
|
||||
web_key:
|
||||
added: Přidán webový klíč
|
||||
activated: Web Key aktivován
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Benutzerschema nicht aktiv
|
||||
NotInactive: Benutzerschema nicht inaktiv
|
||||
NotExists: Benutzerschema existiert nicht
|
||||
ID:
|
||||
Missing: BenutzerschemaID fehlt
|
||||
Invalid: Benutzerschema ist ungültig
|
||||
Data:
|
||||
Invalid: Daten für Benutzerschema ungültig
|
||||
TokenExchange:
|
||||
FeatureDisabled: Die Token-Austauschfunktion ist für Ihre Instanz deaktiviert. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1319,6 +1324,26 @@ EventTypes:
|
||||
deactivated: Benutzerschema deaktiviert
|
||||
reactivated: Benutzerschema reaktiviert
|
||||
deleted: Benutzerschema gelöscht
|
||||
user:
|
||||
created: Benutzer erstellt
|
||||
updated: Benutzer aktualisiert
|
||||
deleted: Benutzer gelöscht
|
||||
email:
|
||||
updated: E-Mail-Adresse geändert
|
||||
verified: E-Mail-Adresse verifiziert
|
||||
verification:
|
||||
failed: E-Mail-Adressenverifizierung fehlgeschlagen
|
||||
code:
|
||||
added: E-Mail-Adressenverifizierungscode generiert
|
||||
sent: E-Mail-Adressenverifizierungscode gesendet
|
||||
phone:
|
||||
updated: Telefonnummer geändert
|
||||
verified: Telefonnummer verifiziert
|
||||
verification:
|
||||
failed: Telefonnummernverifizierung fehlgeschlagen
|
||||
code:
|
||||
added: Telefonnummernverifizierungscode generiert
|
||||
sent: Telefonnummernverifizierungscode gesendet
|
||||
web_key:
|
||||
added: Web Key hinzugefügt
|
||||
activated: Web Key aktiviert
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: User Schema not active
|
||||
NotInactive: User Schema not inactive
|
||||
NotExists: User Schema does not exist
|
||||
ID:
|
||||
Missing: User Schema ID missing
|
||||
Invalid: User Schema invalid
|
||||
Data:
|
||||
Invalid: Data invalid for User Schema
|
||||
TokenExchange:
|
||||
FeatureDisabled: Token Exchange feature is disabled for your instance. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1319,6 +1324,28 @@ EventTypes:
|
||||
deactivated: User Schema deactivated
|
||||
reactivated: User Schema reactivated
|
||||
deleted: User Schema deleted
|
||||
user:
|
||||
created: User created
|
||||
updated: User updated
|
||||
deleted: User deleted
|
||||
email:
|
||||
updated: Email address changed
|
||||
verified: Email address verified
|
||||
verification:
|
||||
failed: Email address verification failed
|
||||
code:
|
||||
added: Email address verification code generated
|
||||
sent: Email address verification code sent
|
||||
phone:
|
||||
updated: Phone number changed
|
||||
verified: Phone number verified
|
||||
verification:
|
||||
failed: Phone number verification failed
|
||||
code:
|
||||
added: Phone number verification code generated
|
||||
sent: Phone number verification code sent
|
||||
|
||||
|
||||
web_key:
|
||||
added: Web Key added
|
||||
activated: Web Key activated
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Esquema de usuario no activo
|
||||
NotInactive: Esquema de usuario no inactivo
|
||||
NotExists: El esquema de usuario no existe
|
||||
ID:
|
||||
Missing: Falta el ID del esquema de usuario
|
||||
Invalid: Esquema de usuario no válido
|
||||
Data:
|
||||
Invalid: Datos no válidos para el esquema de usuario
|
||||
TokenExchange:
|
||||
FeatureDisabled: La función de intercambio de tokens está deshabilitada para su instancia. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1319,6 +1324,26 @@ EventTypes:
|
||||
deactivated: Esquema de usuario desactivado
|
||||
reactivated: Esquema de usuario reactivado
|
||||
deleted: Esquema de usuario eliminado
|
||||
user:
|
||||
created: Usuario creado
|
||||
updated: Usuario actualizado
|
||||
deleted: Usuario eliminado
|
||||
email:
|
||||
updated: Dirección de correo electrónico modificada
|
||||
verified: Dirección de correo electrónico verificada
|
||||
verification:
|
||||
failed: Error en la verificación de la dirección de correo electrónico
|
||||
code:
|
||||
added: Código de verificación de la dirección de correo electrónico generado
|
||||
sent: Código de verificación de la dirección de correo electrónico enviado
|
||||
phone:
|
||||
updated: Número de teléfono modificado
|
||||
verified: Número de teléfono verificado
|
||||
verification:
|
||||
failed: Error en la verificación del número de teléfono
|
||||
code:
|
||||
added: Código de verificación del número de teléfono generado
|
||||
sent: Código de verificación del número de teléfono enviado
|
||||
web_key:
|
||||
added: Clave web añadida
|
||||
activated: Clave web activada
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Schéma utilisateur non actif
|
||||
NotInactive: Le schéma utilisateur n'est pas inactif
|
||||
NotExists: Le schéma utilisateur n'existe pas
|
||||
ID:
|
||||
Missing: ID de schéma utilisateur manquant
|
||||
Invalid: Schéma utilisateur non valide
|
||||
Data:
|
||||
Invalid: Données non valides pour le schéma utilisateur
|
||||
TokenExchange:
|
||||
FeatureDisabled: La fonctionnalité Token Exchange est désactivée pour votre instance. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1314,6 +1319,26 @@ EventTypes:
|
||||
deactivated: Schéma utilisateur désactivé
|
||||
reactivated: Schéma utilisateur réactivé
|
||||
deleted: Schéma utilisateur supprimé
|
||||
user:
|
||||
created: Utilisateur créé
|
||||
updated: Utilisateur mis à jour
|
||||
deleted: Utilisateur supprimé
|
||||
email:
|
||||
updated: Adresse e-mail modifiée
|
||||
verified: Adresse e-mail vérifiée
|
||||
verification:
|
||||
failed: Échec de la vérification de l'adresse e-mail
|
||||
code:
|
||||
added: Code de vérification de l'adresse e-mail généré
|
||||
sent: Code de vérification de l'adresse e-mail envoyé
|
||||
phone:
|
||||
updated: Numéro de téléphone modifié
|
||||
verified: Numéro de téléphone vérifié
|
||||
verification:
|
||||
failed: Échec de la vérification du numéro de téléphone
|
||||
code:
|
||||
added: Code de vérification du numéro de téléphone généré
|
||||
sent: Code de vérification du numéro de téléphone envoyé
|
||||
web_key:
|
||||
added: Clé Web ajoutée
|
||||
activated: Clé Web activée
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Schema utente non attivo
|
||||
NotInactive: Schema utente non inattivo
|
||||
NotExists: Lo schema utente non esiste
|
||||
ID:
|
||||
Missing: ID schema utente mancante
|
||||
Invalid: Schema utente non valido
|
||||
Data:
|
||||
Invalid: Dati non validi per lo schema utente
|
||||
TokenExchange:
|
||||
FeatureDisabled: La funzionalità di scambio token è disabilitata per la tua istanza. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1315,6 +1320,26 @@ EventTypes:
|
||||
deactivated: Schema utente disattivato
|
||||
reactivated: Schema utente riattivato
|
||||
deleted: Schema utente eliminato
|
||||
user:
|
||||
created: Utente creato
|
||||
updated: Utente aggiornato
|
||||
deleted: Utente eliminato
|
||||
email:
|
||||
updated: Indirizzo email modificato
|
||||
verified: Indirizzo email verificato
|
||||
verification:
|
||||
failed: Verifica indirizzo email non riuscita
|
||||
code:
|
||||
added: Codice di verifica indirizzo email generato
|
||||
sent: Codice di verifica indirizzo email inviato
|
||||
phone:
|
||||
updated: Numero di telefono modificato
|
||||
verified: Numero di telefono verificato
|
||||
verification:
|
||||
failed: Verifica numero di telefono non riuscita
|
||||
code:
|
||||
added: Codice di verifica numero di telefono generato
|
||||
sent: Codice di verifica numero di telefono inviato
|
||||
web_key:
|
||||
added: Web Key aggiunto
|
||||
activated: Web Key attivato
|
||||
|
@@ -565,6 +565,11 @@ Errors:
|
||||
NotActive: ユーザースキーマがアクティブではありません
|
||||
NotInactive: ユーザースキーマが非アクティブではありません
|
||||
NotExists: ユーザースキーマが存在しません
|
||||
ID:
|
||||
Missing: ユーザー スキーマ ID がありません
|
||||
Invalid: ユーザー スキーマが無効です
|
||||
Data:
|
||||
Invalid: ユーザー スキーマのデータが無効です
|
||||
TokenExchange:
|
||||
FeatureDisabled: インスタンスではトークン交換機能が無効になっています。 https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1305,6 +1310,26 @@ EventTypes:
|
||||
deactivated: ユーザースキーマが非アクティブ化されました
|
||||
reactivated: ユーザースキーマが再アクティブ化されました
|
||||
deleted: ユーザースキーマが削除されました
|
||||
user:
|
||||
created: ユーザーが作成されました
|
||||
updated: ユーザーが更新されました
|
||||
deleted: ユーザーが削除されました
|
||||
email:
|
||||
updated: メールアドレスが変更されました
|
||||
verified: メールアドレスが確認されました
|
||||
verification:
|
||||
failed: メールアドレスの確認に失敗しました
|
||||
code:
|
||||
added: メールアドレスの確認コードが生成されました
|
||||
sent: メールアドレスの確認コードが送信されました
|
||||
phone:
|
||||
updated: 電話番号が変更されました
|
||||
verified: 電話番号が確認されました
|
||||
verification:
|
||||
failed: 電話番号の確認に失敗しました
|
||||
code:
|
||||
added: 電話番号の確認コードが生成されました
|
||||
sent: 電話番号の確認コードが送信されました
|
||||
web_key:
|
||||
added: Web キーが追加されました
|
||||
activated: Web キーが有効化されました
|
||||
|
@@ -575,6 +575,11 @@ Errors:
|
||||
NotActive: Корисничката шема не е активна
|
||||
NotInactive: Корисничката шема не е неактивна
|
||||
NotExists: Корисничката шема не постои
|
||||
ID:
|
||||
Missing: Недостасува ID на корисничка шема
|
||||
Invalid: Корисничката шема е неважечка
|
||||
Data:
|
||||
Invalid: Податоците не се валидни за корисничка шема
|
||||
TokenExchange:
|
||||
FeatureDisabled: Функцијата за размена на токени е оневозможена на вашиот пример. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1317,6 +1322,26 @@ EventTypes:
|
||||
deactivated: Корисничката шема е деактивирана
|
||||
reactivated: Корисничката шема е реактивирана
|
||||
deleted: Корисничката шема е избришана
|
||||
user:
|
||||
created: Корисникот е создаден
|
||||
updated: Корисникот е ажуриран
|
||||
deleted: Корисникот е избришан
|
||||
email:
|
||||
updated: Адресата на е-пошта е променета
|
||||
verified: Адресата на е-пошта е потврдена
|
||||
verification:
|
||||
failed: Потврдата на адресата на е-пошта не успеа
|
||||
code:
|
||||
added: Генериран е код за потврда на адресата на е-пошта
|
||||
sent: Испратен е код за потврда на адресата на е-пошта
|
||||
phone:
|
||||
updated: Телефонскиот број е променет
|
||||
verified: Телефонскиот број е потврден
|
||||
verification:
|
||||
failed: Потврдата на телефонскиот број не успеа
|
||||
code:
|
||||
added: Генериран е кодот за потврда на телефонскиот број
|
||||
sent: Кодот за потврда на телефонскиот број е испратен
|
||||
web_key:
|
||||
added: Додаден е веб-клуч
|
||||
activated: Веб-клучот е активиран
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Gebruikersschema niet actief
|
||||
NotInactive: Gebruikersschema niet inactief
|
||||
NotExists: Gebruikersschema bestaat niet
|
||||
ID:
|
||||
Missing: Недостасува ID на корисничка шема
|
||||
Invalid: Корисничката шема е неважечка
|
||||
Data:
|
||||
Invalid: Податоците не се валидни за корисничка шема
|
||||
TokenExchange:
|
||||
FeatureDisabled: De Token Exchange-functie is uitgeschakeld voor uw instantie. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1314,6 +1319,26 @@ EventTypes:
|
||||
deactivated: Gebruikersschema gedeactiveerd
|
||||
reactivated: Gebruikersschema opnieuw geactiveerd
|
||||
deleted: Gebruikersschema verwijderd
|
||||
user:
|
||||
created: Gebruiker aangemaakt
|
||||
updated: Gebruiker bijgewerkt
|
||||
deleted: Gebruiker verwijderd
|
||||
email:
|
||||
updated: E-mailadres gewijzigd
|
||||
verified: E-mailadres geverifieerd
|
||||
verification:
|
||||
failed: E-mailadres verificatie mislukt
|
||||
code:
|
||||
added: E-mailadres verificatiecode gegenereerd
|
||||
sent: E-mailadres verificatiecode verzonden
|
||||
phone:
|
||||
updated: Telefoonnummer gewijzigd
|
||||
verified: Telefoonnummer geverifieerd
|
||||
verification:
|
||||
failed: Telefoonnummer verificatie mislukt
|
||||
code:
|
||||
added: Telefoonnummer verificatiecode gegenereerd
|
||||
sent: Telefoonnummer verificatiecode verzonden
|
||||
web_key:
|
||||
added: Web Key toegevoegd
|
||||
activated: Web Key geactiveerd
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Schemat użytkownika nieaktywny
|
||||
NotInactive: Schemat użytkownika nie jest nieaktywny
|
||||
NotExists: Schemat użytkownika nie istnieje
|
||||
ID:
|
||||
Missing: Brak identyfikatora schematu użytkownika
|
||||
Invalid: Nieprawidłowy schemat użytkownika
|
||||
Data:
|
||||
Invalid: Nieprawidłowe dane dla schematu użytkownika
|
||||
TokenExchange:
|
||||
FeatureDisabled: Funkcja wymiany tokenów jest wyłączona dla Twojej instancji. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1319,6 +1324,26 @@ EventTypes:
|
||||
deactivated: Schemat użytkownika dezaktywowany
|
||||
reactivated: Schemat użytkownika został ponownie aktywowany
|
||||
deleted: Schemat użytkownika został usunięty
|
||||
user:
|
||||
created: Użytkownik utworzony
|
||||
updated: Użytkownik zaktualizowany
|
||||
deleted: Użytkownik usunięty
|
||||
email:
|
||||
updated: Adres e-mail zmieniony
|
||||
verified: Adres e-mail zweryfikowany
|
||||
verification:
|
||||
failed: Weryfikacja adresu e-mail nie powiodła się
|
||||
code:
|
||||
added: Wygenerowano kod weryfikacyjny adresu e-mail
|
||||
sent: Wysłano kod weryfikacyjny adresu e-mail
|
||||
phone:
|
||||
updated: Numer telefonu zmieniony
|
||||
verified: Numer telefonu zweryfikowany
|
||||
verification:
|
||||
failed: Weryfikacja numeru telefonu nie powiodła się
|
||||
code:
|
||||
added: Wygenerowano kod weryfikacyjny numeru telefonu
|
||||
sent: Wysłano kod weryfikacyjny numeru telefonu
|
||||
web_key:
|
||||
added: Dodano klucz internetowy
|
||||
activated: Klucz internetowy aktywowano
|
||||
|
@@ -571,6 +571,11 @@ Errors:
|
||||
NotActive: Esquema do usuário não ativo
|
||||
NotInactive: Esquema do usuário não inativo
|
||||
NotExists: O esquema do usuário não existe
|
||||
ID:
|
||||
Missing: ID do esquema do utilizador em falta
|
||||
Invalid: Esquema de utilizador inválido
|
||||
Data:
|
||||
Invalid: Dados inválidos para o esquema do utilizador
|
||||
TokenExchange:
|
||||
FeatureDisabled: O recurso Token Exchange está desabilitado para sua instância. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1311,6 +1316,26 @@ EventTypes:
|
||||
deactivated: Esquema de usuário desativado
|
||||
reactivated: Esquema do usuário reativado
|
||||
deleted: Esquema do usuário excluído
|
||||
user:
|
||||
created: Utilizador criado
|
||||
updated: Utilizador atualizado
|
||||
deleted: Utilizador excluído
|
||||
email:
|
||||
updated: Endereço de e-mail alterado
|
||||
verified: Endereço de e-mail verificado
|
||||
verification:
|
||||
failed: Falha na verificação do endereço de e-mail
|
||||
code:
|
||||
added: Código de verificação do endereço de e-mail gerado
|
||||
sent: Código de verificação do endereço de e-mail enviado
|
||||
phone:
|
||||
updated: Número de telefone alterado
|
||||
verified: Número de telefone verificado
|
||||
verification:
|
||||
failed: Falha na verificação do número de telefone
|
||||
code:
|
||||
added: Código de verificação do número de telefone gerado
|
||||
sent: Código de verificação do número de telefone enviado
|
||||
web_key:
|
||||
added: Chave Web adicionada
|
||||
activated: Chave Web ativada
|
||||
|
@@ -565,6 +565,11 @@ Errors:
|
||||
NotActive: Пользовательская схема не активна
|
||||
NotInactive: Пользовательская схема не неактивна
|
||||
NotExists: Пользовательская схема не существует
|
||||
ID:
|
||||
Missing: Отсутствует идентификатор схемы пользователя
|
||||
Invalid: Недействительная схема пользователя
|
||||
Data:
|
||||
Invalid: Данные недействительны для схемы пользователя
|
||||
TokenExchange:
|
||||
FeatureDisabled: Функция обмена токенами отключена для вашего экземпляра. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1305,6 +1310,26 @@ EventTypes:
|
||||
deactivated: Пользовательская схема деактивирована
|
||||
reactivated: Пользовательская схема повторно активирована
|
||||
deleted: Пользовательская схема удалена
|
||||
user:
|
||||
created: Пользователь создан
|
||||
updated: Пользователь обновлен
|
||||
deleted: Пользователь удален
|
||||
email:
|
||||
updated: Адрес электронной почты изменен
|
||||
verified: Адрес электронной почты проверен
|
||||
verification:
|
||||
failed: Проверка адреса электронной почты не удалась
|
||||
code:
|
||||
added: Сгенерирован код проверки адреса электронной почты
|
||||
sent: Отправлен код проверки адреса электронной почты
|
||||
phone:
|
||||
updated: Номер телефона изменен
|
||||
verified: Номер телефона проверен
|
||||
verification:
|
||||
failed: Проверка номера телефона не удалась
|
||||
code:
|
||||
added: Сгенерирован код проверки номера телефона
|
||||
sent: Отправлен код проверки номера телефона
|
||||
web_key:
|
||||
added: Добавлен веб-ключ
|
||||
activated: Веб-ключ активирован
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: Användarschema inte aktivt
|
||||
NotInactive: Användarschema inte inaktivt
|
||||
NotExists: Användarschema existerar inte
|
||||
ID:
|
||||
Missing: Användarschema-ID saknas
|
||||
Invalid: Ogiltigt användarschema
|
||||
Data:
|
||||
Invalid: Data ogiltig för användarschema
|
||||
TokenExchange:
|
||||
FeatureDisabled: Token Exchange-funktionen är inaktiverad för din instans. https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1319,6 +1324,26 @@ EventTypes:
|
||||
deactivated: Användarschema avaktiverat
|
||||
reactivated: Användarschema återaktiverat
|
||||
deleted: Användarschema borttaget
|
||||
user:
|
||||
created: Användare skapad
|
||||
updated: Användaren uppdaterad
|
||||
deleted: Användare raderad
|
||||
email:
|
||||
updated: E-postadress ändrad
|
||||
verified: E-postadress verifierad
|
||||
verification:
|
||||
failed: E-postadressverifiering misslyckades
|
||||
code:
|
||||
added: Verifieringskod för e-postadress genererad
|
||||
sent: E-postadressens verifieringskod har skickats
|
||||
phone:
|
||||
updated: Telefonnummer ändrat
|
||||
verified: Telefonnummer verifierat
|
||||
verification:
|
||||
failed: Verifiering av telefonnummer misslyckades
|
||||
code:
|
||||
added: Verifieringskod för telefonnummer genererad
|
||||
sent: Verifieringskoden för telefonnummer har skickats
|
||||
web_key:
|
||||
added: Webbnyckel har lagts till
|
||||
activated: Webbnyckel aktiverad
|
||||
|
@@ -576,6 +576,11 @@ Errors:
|
||||
NotActive: 用户架构未激活
|
||||
NotInactive: 用户架构未处于非活动状态
|
||||
NotExists: 用户架构不存在
|
||||
ID:
|
||||
Missing: 缺少用户架构 ID
|
||||
Invalid: 用户架构无效
|
||||
Data:
|
||||
Invalid: 用户架构的数据无效
|
||||
TokenExchange:
|
||||
FeatureDisabled: 您的实例已禁用令牌交换功能。 https://zitadel.com/docs/apis/resources/feature_service_v2/feature-service-set-instance-features
|
||||
Token:
|
||||
@@ -1318,6 +1323,26 @@ EventTypes:
|
||||
deactivated: 用户架构已停用
|
||||
reactivated: 用户架构已重新激活
|
||||
deleted: 用户架构已删除
|
||||
user:
|
||||
created: 用户已创建
|
||||
updated: 用户已更新
|
||||
deleted: 用户已删除
|
||||
email:
|
||||
updated: 电子邮件地址已更改
|
||||
verified: 电子邮件地址已验证
|
||||
verification:
|
||||
failed: 电子邮件地址验证失败
|
||||
code:
|
||||
added: 电子邮件地址验证码已生成
|
||||
sent: 电子邮件地址验证码已发送
|
||||
phone:
|
||||
updated: 电话号码已更改
|
||||
verified: 电话号码已验证
|
||||
verification:
|
||||
failed: 电话号码验证失败
|
||||
code:
|
||||
added: 电话号码验证码已生成
|
||||
sent: 电话号码验证码已发送
|
||||
web_key:
|
||||
added: 已添加 Web Key
|
||||
activated: 已激活 Web Key
|
||||
|
Reference in New Issue
Block a user