feat: implement user schema management (#7416)

This PR adds the functionality to manage user schemas through the new user schema service.
It includes the possibility to create a basic JSON schema and also provides a way on defining permissions (read, write) for owner and self context with an annotation.

Further annotations for OIDC claims and SAML attribute mappings will follow.

A guide on how to create a schema and assign permissions has been started. It will be extended though out the process of implementing the schema and users based on those.

Note:
This feature is in an early stage and therefore not enabled by default. To test it out, please enable the UserSchema feature flag on your instance / system though the feature service.
This commit is contained in:
Livio Spring
2024-03-12 14:50:13 +01:00
committed by GitHub
parent 2a39cc16f5
commit 0e181b218c
61 changed files with 3614 additions and 35 deletions

View File

@@ -0,0 +1,150 @@
package schema
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/zerrors"
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
)
func (s *Server) CreateUserSchema(ctx context.Context, req *schema.CreateUserSchemaRequest) (*schema.CreateUserSchemaResponse, error) {
if err := checkUserSchemaEnabled(ctx); err != nil {
return nil, err
}
userSchema, err := createUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
id, details, err := s.command.CreateUserSchema(ctx, userSchema)
if err != nil {
return nil, err
}
return &schema.CreateUserSchemaResponse{
Id: id,
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) UpdateUserSchema(ctx context.Context, req *schema.UpdateUserSchemaRequest) (*schema.UpdateUserSchemaResponse, error) {
if err := checkUserSchemaEnabled(ctx); err != nil {
return nil, err
}
userSchema, err := updateUserSchemaToCommand(req, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
details, err := s.command.UpdateUserSchema(ctx, userSchema)
if err != nil {
return nil, err
}
return &schema.UpdateUserSchemaResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) DeactivateUserSchema(ctx context.Context, req *schema.DeactivateUserSchemaRequest) (*schema.DeactivateUserSchemaResponse, error) {
if err := checkUserSchemaEnabled(ctx); err != nil {
return nil, err
}
details, err := s.command.DeactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
return &schema.DeactivateUserSchemaResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) ReactivateUserSchema(ctx context.Context, req *schema.ReactivateUserSchemaRequest) (*schema.ReactivateUserSchemaResponse, error) {
if err := checkUserSchemaEnabled(ctx); err != nil {
return nil, err
}
details, err := s.command.ReactivateUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
return &schema.ReactivateUserSchemaResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) DeleteUserSchema(ctx context.Context, req *schema.DeleteUserSchemaRequest) (*schema.DeleteUserSchemaResponse, error) {
if err := checkUserSchemaEnabled(ctx); err != nil {
return nil, err
}
details, err := s.command.DeleteUserSchema(ctx, req.GetId(), authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
return &schema.DeleteUserSchemaResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func checkUserSchemaEnabled(ctx context.Context) error {
if authz.GetInstance(ctx).Features().UserSchema {
return nil
}
return zerrors.ThrowPreconditionFailed(nil, "SCHEMA-SFjk3", "Errors.UserSchema.NotEnabled")
}
func createUserSchemaToCommand(req *schema.CreateUserSchemaRequest, resourceOwner string) (*command.CreateUserSchema, error) {
schema, err := req.GetSchema().MarshalJSON()
if err != nil {
return nil, err
}
return &command.CreateUserSchema{
ResourceOwner: resourceOwner,
Type: req.GetType(),
Schema: schema,
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
}, nil
}
func updateUserSchemaToCommand(req *schema.UpdateUserSchemaRequest, resourceOwner string) (*command.UpdateUserSchema, error) {
schema, err := req.GetSchema().MarshalJSON()
if err != nil {
return nil, err
}
return &command.UpdateUserSchema{
ID: req.GetId(),
ResourceOwner: resourceOwner,
Type: req.Type,
Schema: schema,
PossibleAuthenticators: authenticatorsToDomain(req.GetPossibleAuthenticators()),
}, nil
}
func authenticatorsToDomain(authenticators []schema.AuthenticatorType) []domain.AuthenticatorType {
types := make([]domain.AuthenticatorType, len(authenticators))
for i, authenticator := range authenticators {
types[i] = authenticatorTypeToDomain(authenticator)
}
return types
}
func authenticatorTypeToDomain(authenticator schema.AuthenticatorType) domain.AuthenticatorType {
switch authenticator {
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED:
return domain.AuthenticatorTypeUnspecified
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME:
return domain.AuthenticatorTypeUsername
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_PASSWORD:
return domain.AuthenticatorTypePassword
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_WEBAUTHN:
return domain.AuthenticatorTypeWebAuthN
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_TOTP:
return domain.AuthenticatorTypeTOTP
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_EMAIL:
return domain.AuthenticatorTypeOTPEmail
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_OTP_SMS:
return domain.AuthenticatorTypeOTPSMS
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_AUTHENTICATION_KEY:
return domain.AuthenticatorTypeAuthenticationKey
case schema.AuthenticatorType_AUTHENTICATOR_TYPE_IDENTITY_PROVIDER:
return domain.AuthenticatorTypeIdentityProvider
default:
return domain.AuthenticatorTypeUnspecified
}
}

View File

@@ -0,0 +1,812 @@
//go:build integration
package schema_test
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
feature "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
)
var (
CTX context.Context
Tester *integration.Tester
Client schema.UserSchemaServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, _, cancel := integration.Contexts(5 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
CTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
Client = Tester.Client.UserSchemaV3
return m.Run()
}())
}
func ensureFeatureEnabled(t *testing.T) {
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(t, err)
if f.UserSchema.GetEnabled() {
return
}
_, err = Tester.Client.FeatureV2.SetInstanceFeatures(CTX, &feature.SetInstanceFeaturesRequest{
UserSchema: gu.Ptr(true),
})
require.NoError(t, err)
retryDuration := time.Minute
if ctxDeadline, ok := CTX.Deadline(); ok {
retryDuration = time.Until(ctxDeadline)
}
require.EventuallyWithT(t,
func(ttt *assert.CollectT) {
f, err := Tester.Client.FeatureV2.GetInstanceFeatures(CTX, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(ttt, err)
if f.UserSchema.GetEnabled() {
return
}
},
retryDuration,
100*time.Millisecond,
"timed out waiting for ensuring instance feature")
}
func TestServer_CreateUserSchema(t *testing.T) {
tests := []struct {
name string
ctx context.Context
req *schema.CreateUserSchemaRequest
want *schema.CreateUserSchemaResponse
wantErr bool
}{
{
name: "missing permission, error",
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
},
wantErr: true,
},
{
name: "empty type",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: "",
},
wantErr: true,
},
{
name: "empty schema, error",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
},
wantErr: true,
},
{
name: "invalid schema, error",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string"
}
}
}
`))
require.NoError(t, err)
return s
}(),
},
},
wantErr: true,
},
{
name: "no authenticators, ok",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
},
want: &schema.CreateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "invalid authenticator, error",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
},
},
wantErr: true,
},
{
name: "with authenticator, ok",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
},
},
want: &schema.CreateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "with invalid permission, error",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string",
"urn:zitadel:schema:permission": "read"
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
},
},
wantErr: true,
},
{
name: "with valid permission, ok",
ctx: CTX,
req: &schema.CreateUserSchemaRequest{
Type: fmt.Sprint(time.Now().UnixNano() + 1),
DataType: &schema.CreateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string",
"urn:zitadel:schema:permission": {
"owner": "rw",
"self": "r"
}
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
},
},
want: &schema.CreateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensureFeatureEnabled(t)
got, err := Client.CreateUserSchema(tt.ctx, tt.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
assert.NotEmpty(t, got.GetId())
})
}
}
func TestServer_UpdateUserSchema(t *testing.T) {
type args struct {
ctx context.Context
req *schema.UpdateUserSchemaRequest
}
tests := []struct {
name string
prepare func(request *schema.UpdateUserSchemaRequest) error
args args
want *schema.UpdateUserSchemaResponse
wantErr bool
}{
{
name: "missing permission, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: Tester.WithAuthorization(context.Background(), integration.OrgOwner),
req: &schema.UpdateUserSchemaRequest{
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
},
},
wantErr: true,
},
{
name: "missing id, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{},
},
wantErr: true,
},
{
name: "not existing, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
request.Id = "notexisting"
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{},
},
wantErr: true,
},
{
name: "empty type, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
Type: gu.Ptr(""),
},
},
wantErr: true,
},
{
name: "update type, ok",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
},
},
want: &schema.UpdateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "empty schema, ok",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
DataType: &schema.UpdateUserSchemaRequest_Schema{},
},
},
want: &schema.UpdateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "invalid schema, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
DataType: &schema.UpdateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string",
"required": true
},
"description": {
"type": "string"
}
}
}
`))
require.NoError(t, err)
return s
}(),
},
},
},
wantErr: true,
},
{
name: "update schema, ok",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
DataType: &schema.UpdateUserSchemaRequest_Schema{
Schema: func() *structpb.Struct {
s := new(structpb.Struct)
err := s.UnmarshalJSON([]byte(`
{
"$schema": "urn:zitadel:schema:v1",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
}
},
"required": ["name"]
}
`))
require.NoError(t, err)
return s
}(),
},
},
},
want: &schema.UpdateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "invalid authenticator, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_UNSPECIFIED,
},
},
},
wantErr: true,
},
{
name: "update authenticator, ok",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
PossibleAuthenticators: []schema.AuthenticatorType{
schema.AuthenticatorType_AUTHENTICATOR_TYPE_USERNAME,
},
},
},
want: &schema.UpdateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "inactive, error",
prepare: func(request *schema.UpdateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
Id: schemaID,
})
require.NoError(t, err)
request.Id = schemaID
return nil
},
args: args{
ctx: CTX,
req: &schema.UpdateUserSchemaRequest{
Type: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensureFeatureEnabled(t)
err := tt.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.UpdateUserSchema(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_DeactivateUserSchema(t *testing.T) {
type args struct {
ctx context.Context
req *schema.DeactivateUserSchemaRequest
prepare func(request *schema.DeactivateUserSchemaRequest) error
}
tests := []struct {
name string
args args
want *schema.DeactivateUserSchemaResponse
wantErr bool
}{
{
name: "not existing, error",
args: args{
CTX,
&schema.DeactivateUserSchemaRequest{
Id: "notexisting",
},
func(request *schema.DeactivateUserSchemaRequest) error { return nil },
},
wantErr: true,
},
{
name: "active, ok",
args: args{
CTX,
&schema.DeactivateUserSchemaRequest{},
func(request *schema.DeactivateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
},
want: &schema.DeactivateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "inactive, error",
args: args{
CTX,
&schema.DeactivateUserSchemaRequest{},
func(request *schema.DeactivateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
Id: schemaID,
})
return err
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensureFeatureEnabled(t)
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.DeactivateUserSchema(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_ReactivateUserSchema(t *testing.T) {
type args struct {
ctx context.Context
req *schema.ReactivateUserSchemaRequest
prepare func(request *schema.ReactivateUserSchemaRequest) error
}
tests := []struct {
name string
args args
want *schema.ReactivateUserSchemaResponse
wantErr bool
}{
{
name: "not existing, error",
args: args{
CTX,
&schema.ReactivateUserSchemaRequest{
Id: "notexisting",
},
func(request *schema.ReactivateUserSchemaRequest) error { return nil },
},
wantErr: true,
},
{
name: "active, error",
args: args{
ctx: CTX,
req: &schema.ReactivateUserSchemaRequest{},
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
},
wantErr: true,
},
{
name: "inactive, ok",
args: args{
ctx: CTX,
req: &schema.ReactivateUserSchemaRequest{},
prepare: func(request *schema.ReactivateUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
_, err := Client.DeactivateUserSchema(CTX, &schema.DeactivateUserSchemaRequest{
Id: schemaID,
})
return err
},
},
want: &schema.ReactivateUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensureFeatureEnabled(t)
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.ReactivateUserSchema(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_DeleteUserSchema(t *testing.T) {
type args struct {
ctx context.Context
req *schema.DeleteUserSchemaRequest
prepare func(request *schema.DeleteUserSchemaRequest) error
}
tests := []struct {
name string
args args
want *schema.DeleteUserSchemaResponse
wantErr bool
}{
{
name: "not existing, error",
args: args{
CTX,
&schema.DeleteUserSchemaRequest{
Id: "notexisting",
},
func(request *schema.DeleteUserSchemaRequest) error { return nil },
},
wantErr: true,
},
{
name: "delete, ok",
args: args{
ctx: CTX,
req: &schema.DeleteUserSchemaRequest{},
prepare: func(request *schema.DeleteUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
return nil
},
},
want: &schema.DeleteUserSchemaResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Instance.InstanceID(),
},
},
},
{
name: "deleted, error",
args: args{
ctx: CTX,
req: &schema.DeleteUserSchemaRequest{},
prepare: func(request *schema.DeleteUserSchemaRequest) error {
schemaID := Tester.CreateUserSchema(CTX, t).GetId()
request.Id = schemaID
_, err := Client.DeleteUserSchema(CTX, &schema.DeleteUserSchemaRequest{
Id: schemaID,
})
return err
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ensureFeatureEnabled(t)
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.DeleteUserSchema(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,51 @@
package schema
import (
"google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/query"
schema "github.com/zitadel/zitadel/pkg/grpc/user/schema/v3alpha"
)
var _ schema.UserSchemaServiceServer = (*Server)(nil)
type Server struct {
schema.UnimplementedUserSchemaServiceServer
command *command.Commands
query *query.Queries
}
type Config struct{}
func CreateServer(
command *command.Commands,
query *query.Queries,
) *Server {
return &Server{
command: command,
query: query,
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
schema.RegisterUserSchemaServiceServer(grpcServer, s)
}
func (s *Server) AppName() string {
return schema.UserSchemaService_ServiceDesc.ServiceName
}
func (s *Server) MethodPrefix() string {
return schema.UserSchemaService_ServiceDesc.ServiceName
}
func (s *Server) AuthMethods() authz.MethodMapping {
return schema.UserSchemaService_AuthMethods
}
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
return schema.RegisterUserSchemaServiceHandler
}