mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +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
|
||||
}
|
Reference in New Issue
Block a user