feat: new es testing2 (#1428)

* fix: org tests

* fix: org tests

* fix: user grant test

* fix: user grant test

* fix: project and project role test

* fix: project grant test

* fix: project grant test

* fix: project member, grant member, app changed tests

* fix: application tests

* fix: application tests

* fix: add oidc app test

* fix: add oidc app test

* fix: add api keys test

* fix: iam policies

* fix: iam and org member tests

* fix: idp config tests

* fix: iam tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: user tests

* fix: org domain test

* fix: org tests

* fix: org tests

* fix: implement org idps

* fix: pr requests

* fix: email tests

* fix: fix idp check

* fix: fix user profile
This commit is contained in:
Fabi
2021-03-19 11:12:56 +01:00
committed by GitHub
parent b01f277e4b
commit 3f345b1ade
102 changed files with 17481 additions and 269 deletions

View File

@@ -170,7 +170,7 @@ func (i *IDPProvider) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfi
if _, i := existing.GetIDP(idpConfigID); i != nil {
return i, nil
}
return nil, errors.ThrowNotFound(nil, "EVENT-4m0fs", "Errors.Org.IdpNotExisting")
return nil, errors.ThrowNotFound(nil, "EVENT-4m0fs", "Errors.IDP.NotExisting")
}
func (i *IDPProvider) getOrgByID(ctx context.Context, orgID string) (*org_model.Org, error) {
@@ -220,5 +220,5 @@ func (u *IDPProvider) getDefaultIDPConfig(ctx context.Context, idpConfigID strin
if _, existingIDP := existing.GetIDP(idpConfigID); existingIDP != nil {
return existingIDP, nil
}
return nil, errors.ThrowNotFound(nil, "EVENT-4M=Fs", "Errors.IAM.IdpNotExisting")
return nil, errors.ThrowNotFound(nil, "EVENT-4M=Fs", "Errors.IDP.NotExisting")
}

View File

@@ -181,7 +181,7 @@ func (i *ExternalIDP) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfi
if _, i := existing.GetIDP(idpConfigID); i != nil {
return i, nil
}
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2n8Fh", "Errors.Org.IdpNotExisting")
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2n8Fh", "Errors.IDP.NotExisting")
}
func (i *ExternalIDP) getOrgByID(ctx context.Context, orgID string) (*org_model.Org, error) {
@@ -231,5 +231,5 @@ func (u *ExternalIDP) getDefaultIDPConfig(ctx context.Context, idpConfigID strin
if _, existingIDP := existing.GetIDP(idpConfigID); existingIDP != nil {
return existingIDP, nil
}
return nil, caos_errs.ThrowNotFound(nil, "EVENT-49O0f", "Errors.IAM.IdpNotExisting")
return nil, caos_errs.ThrowNotFound(nil, "EVENT-49O0f", "Errors.IDP.NotExisting")
}

View File

@@ -221,6 +221,17 @@ func ModelIDPProviderTypeToPb(typ iam_model.IDPProviderType) idp_pb.IDPOwnerType
}
}
func IDPProviderTypeFromPb(typ idp_pb.IDPOwnerType) domain.IdentityProviderType {
switch typ {
case idp_pb.IDPOwnerType_IDP_OWNER_TYPE_ORG:
return domain.IdentityProviderTypeOrg
case idp_pb.IDPOwnerType_IDP_OWNER_TYPE_SYSTEM:
return domain.IdentityProviderTypeSystem
default:
return domain.IdentityProviderTypeOrg
}
}
func IDPIDQueryToModel(query *idp_pb.IDPIDQuery) *iam_model.IDPConfigSearchQuery {
return &iam_model.IDPConfigSearchQuery{
Key: iam_model.IDPConfigSearchKeyIdpConfigID, //TODO: whats the difference between idpconfigid and aggregateid search key?

View File

@@ -6,29 +6,84 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/caos/zitadel/internal/api/authz"
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
object_pb "github.com/caos/zitadel/internal/api/grpc/object"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func (s *Server) GetOrgIDPByID(ctx context.Context, req *mgmt_pb.GetOrgIDPByIDRequest) (*mgmt_pb.GetOrgIDPByIDResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOrgIDPByID not implemented")
idp, err := s.org.IDPConfigByID(ctx, req.Id)
if err != nil {
return nil, err
}
return &mgmt_pb.GetOrgIDPByIDResponse{Idp: idp_grpc.ModelIDPViewToPb(idp)}, nil
}
func (s *Server) ListOrgIDPs(ctx context.Context, req *mgmt_pb.ListOrgIDPsRequest) (*mgmt_pb.ListOrgIDPsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListOrgIDPs not implemented")
resp, err := s.org.SearchIDPConfigs(ctx, listIDPsToModel(req))
if err != nil {
return nil, err
}
return &mgmt_pb.ListOrgIDPsResponse{
Result: idp_grpc.IDPViewsToPb(resp.Result),
Details: object_pb.ToListDetails(resp.TotalResult, resp.Sequence, resp.Timestamp),
}, nil
}
func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRequest) (*mgmt_pb.AddOrgOIDCIDPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method AddOrgOIDCIDP not implemented")
config, err := s.command.AddDefaultIDPConfig(ctx, addOIDCIDPRequestToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddOrgOIDCIDPResponse{
IdpId: config.AggregateID,
Details: object_pb.ToDetailsPb(
config.Sequence,
config.ChangeDate,
config.ResourceOwner,
),
}, nil
}
func (s *Server) DeactivateOrgIDP(ctx context.Context, req *mgmt_pb.DeactivateOrgIDPRequest) (*mgmt_pb.DeactivateOrgIDPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeactivateOrgIDP not implemented")
objectDetails, err := s.command.DeactivateDefaultIDPConfig(ctx, req.IdpId)
if err != nil {
return nil, err
}
return &mgmt_pb.DeactivateOrgIDPResponse{Details: object_pb.DomainToDetailsPb(objectDetails)}, nil
}
func (s *Server) ReactivateOrgIDP(ctx context.Context, req *mgmt_pb.ReactivateOrgIDPRequest) (*mgmt_pb.ReactivateOrgIDPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ReactivateOrgIDP not implemented")
objectDetails, err := s.command.ReactivateDefaultIDPConfig(ctx, req.IdpId)
if err != nil {
return nil, err
}
return &mgmt_pb.ReactivateOrgIDPResponse{Details: object_pb.DomainToDetailsPb(objectDetails)}, nil
}
func (s *Server) RemoveOrgIDP(ctx context.Context, req *mgmt_pb.RemoveOrgIDPRequest) (*mgmt_pb.RemoveOrgIDPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveOrgIDP not implemented")
idpProviders, err := s.org.GetIDPProvidersByIDPConfigID(ctx, authz.GetCtxData(ctx).OrgID, req.IdpId)
if err != nil {
return nil, err
}
externalIDPs, err := s.user.ExternalIDPsByIDPConfigID(ctx, req.IdpId)
if err != nil {
return nil, err
}
_, err = s.command.RemoveDefaultIDPConfig(ctx, req.IdpId, idpProviderViewsToDomain(idpProviders), externalIDPViewsToDomain(externalIDPs)...)
if err != nil {
return nil, err
}
return &mgmt_pb.RemoveOrgIDPResponse{}, nil
}
func (s *Server) UpdateOrgIDP(ctx context.Context, req *mgmt_pb.UpdateOrgIDPRequest) (*mgmt_pb.UpdateOrgIDPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateOrgIDP not implemented")
config, err := s.command.ChangeDefaultIDPConfig(ctx, updateIDPToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateOrgIDPResponse{
Details: object_pb.ToDetailsPb(
config.Sequence,
config.ChangeDate,
config.ResourceOwner,
),
}, nil
}
func (s *Server) UpdateOrgIDPOIDCConfig(ctx context.Context, req *mgmt_pb.UpdateOrgIDPOIDCConfigRequest) (*mgmt_pb.UpdateOrgIDPOIDCConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateOrgIDPOIDCConfig not implemented")

View File

@@ -0,0 +1,121 @@
package management
import (
idp_grpc "github.com/caos/zitadel/internal/api/grpc/idp"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore/v1/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
user_model "github.com/caos/zitadel/internal/user/model"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func addOIDCIDPRequestToDomain(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.IDPConfig {
return &domain.IDPConfig{
Name: req.Name,
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
Type: domain.IDPConfigTypeOIDC,
}
}
func addOIDCIDPRequestToDomainOIDCIDPConfig(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.OIDCIDPConfig {
return &domain.OIDCIDPConfig{
ClientID: req.ClientId,
ClientSecretString: req.ClientSecret,
Issuer: req.Issuer,
Scopes: req.Scopes,
IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping),
UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping),
}
}
func updateIDPToDomain(req *mgmt_pb.UpdateOrgIDPRequest) *domain.IDPConfig {
return &domain.IDPConfig{
IDPConfigID: req.IdpId,
Name: req.Name,
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
}
}
func updateOIDCConfigToDomain(req *mgmt_pb.UpdateOrgIDPOIDCConfigRequest) *domain.OIDCIDPConfig {
return &domain.OIDCIDPConfig{
IDPConfigID: req.IdpId,
ClientID: req.ClientId,
ClientSecretString: req.ClientSecret,
Issuer: req.Issuer,
Scopes: req.Scopes,
IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping),
UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping),
}
}
func listIDPsToModel(req *mgmt_pb.ListOrgIDPsRequest) *iam_model.IDPConfigSearchRequest {
offset, limit, asc := object.ListQueryToModel(req.Query)
return &iam_model.IDPConfigSearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: idp_grpc.FieldNameToModel(req.SortingColumn),
Queries: idpQueriesToModel(req.Queries),
}
}
func idpQueriesToModel(queries []*mgmt_pb.IDPQuery) []*iam_model.IDPConfigSearchQuery {
q := make([]*iam_model.IDPConfigSearchQuery, len(queries))
for i, query := range queries {
q[i] = idpQueryToModel(query)
}
return q
}
func idpQueryToModel(query *mgmt_pb.IDPQuery) *iam_model.IDPConfigSearchQuery {
switch q := query.Query.(type) {
case *mgmt_pb.IDPQuery_IdpNameQuery:
return idp_grpc.IDPNameQueryToModel(q.IdpNameQuery)
case *mgmt_pb.IDPQuery_IdpIdQuery:
return idp_grpc.IDPIDQueryToModel(q.IdpIdQuery)
default:
return nil
}
}
func idpProviderViewsToDomain(idps []*iam_model.IDPProviderView) []*domain.IDPProvider {
idpProvider := make([]*domain.IDPProvider, len(idps))
for i, idp := range idps {
idpProvider[i] = &domain.IDPProvider{
ObjectRoot: models.ObjectRoot{
AggregateID: idp.AggregateID,
},
IDPConfigID: idp.IDPConfigID,
Type: idpConfigTypeToDomain(idp.IDPProviderType),
}
}
return idpProvider
}
func idpConfigTypeToDomain(idpType iam_model.IDPProviderType) domain.IdentityProviderType {
switch idpType {
case iam_model.IDPProviderTypeOrg:
return domain.IdentityProviderTypeOrg
default:
return domain.IdentityProviderTypeSystem
}
}
func externalIDPViewsToDomain(idps []*user_model.ExternalIDPView) []*domain.ExternalIDP {
externalIDPs := make([]*domain.ExternalIDP, len(idps))
for i, idp := range idps {
externalIDPs[i] = &domain.ExternalIDP{
ObjectRoot: models.ObjectRoot{
AggregateID: idp.UserID,
ResourceOwner: idp.ResourceOwner,
},
IDPConfigID: idp.IDPConfigID,
ExternalUserID: idp.ExternalUserID,
DisplayName: idp.UserDisplayName,
}
}
return externalIDPs
}

View File

@@ -0,0 +1,149 @@
package management
import (
"testing"
"github.com/caos/zitadel/internal/test"
"github.com/caos/zitadel/pkg/grpc/idp"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func Test_addOIDCIDPRequestToDomain(t *testing.T) {
type args struct {
req *mgmt_pb.AddOrgOIDCIDPRequest
}
tests := []struct {
name string
args args
}{
{
name: "all fields filled",
args: args{
req: &mgmt_pb.AddOrgOIDCIDPRequest{
Name: "ZITADEL",
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
ClientId: "test1234",
ClientSecret: "test4321",
Issuer: "zitadel.ch",
Scopes: []string{"email", "profile"},
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addOIDCIDPRequestToDomain(tt.args.req)
test.AssertFieldsMapped(t, got,
"ObjectRoot",
"OIDCConfig.ClientSecret",
"OIDCConfig.ObjectRoot",
"OIDCConfig.IDPConfigID",
"IDPConfigID",
"State",
"Type", //TODO: default (0) is oidc
)
})
}
}
func Test_addOIDCIDPRequestToDomainOIDCIDPConfig(t *testing.T) {
type args struct {
req *mgmt_pb.AddOrgOIDCIDPRequest
}
tests := []struct {
name string
args args
}{
{
name: "all fields filled",
args: args{
req: &mgmt_pb.AddOrgOIDCIDPRequest{
ClientId: "test1234",
ClientSecret: "test4321",
Issuer: "zitadel.ch",
Scopes: []string{"email", "profile"},
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := addOIDCIDPRequestToDomainOIDCIDPConfig(tt.args.req)
test.AssertFieldsMapped(t, got,
"ObjectRoot",
"ClientSecret", //TODO: is client secret string enough for backend?
"IDPConfigID",
)
})
}
}
func Test_updateIDPToDomain(t *testing.T) {
type args struct {
req *mgmt_pb.UpdateOrgIDPRequest
}
tests := []struct {
name string
args args
}{
{
name: "all fields filled",
args: args{
req: &mgmt_pb.UpdateOrgIDPRequest{
IdpId: "13523",
Name: "new name",
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateIDPToDomain(tt.args.req)
test.AssertFieldsMapped(t, got,
"ObjectRoot",
"OIDCConfig",
"State",
"Type", //TODO: type should not be changeable
)
})
}
}
func Test_updateOIDCConfigToDomain(t *testing.T) {
type args struct {
req *mgmt_pb.UpdateOrgIDPOIDCConfigRequest
}
tests := []struct {
name string
args args
}{
{
name: "all fields filled",
args: args{
req: &mgmt_pb.UpdateOrgIDPOIDCConfigRequest{
IdpId: "4208",
Issuer: "zitadel.ch",
ClientId: "ZITEADEL",
ClientSecret: "i'm so secret",
Scopes: []string{"profile"},
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := updateOIDCConfigToDomain(tt.args.req)
test.AssertFieldsMapped(t, got,
"ObjectRoot",
"ClientSecret",
)
})
}
}

View File

@@ -79,7 +79,7 @@ func (s *Server) ListLoginPolicyIDPs(ctx context.Context, req *mgmt_pb.ListLogin
}
func (s *Server) AddIDPToLoginPolicy(ctx context.Context, req *mgmt_pb.AddIDPToLoginPolicyRequest) (*mgmt_pb.AddIDPToLoginPolicyResponse, error) {
idp, err := s.command.AddIDPProviderToLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, &domain.IDPProvider{IDPConfigID: req.IdpId}) //TODO: old way was to also add type but this doesnt make sense in my point of view
idp, err := s.command.AddIDPProviderToLoginPolicy(ctx, authz.GetCtxData(ctx).OrgID, &domain.IDPProvider{IDPConfigID: req.IdpId, Type: idp.IDPProviderTypeFromPb(req.OwnerType)})
if err != nil {
return nil, err
}

View File

@@ -178,7 +178,7 @@ func (i *IDPProvider) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfi
if _, i := existing.GetIDP(idpConfigID); i != nil {
return i, nil
}
return nil, errors.ThrowNotFound(nil, "EVENT-2m9fS", "Errors.Org.IdpNotExisting")
return nil, errors.ThrowNotFound(nil, "EVENT-2m9fS", "Errors.IDP.NotExisting")
}
func (i *IDPProvider) getOrgByID(ctx context.Context, orgID string) (*org_model.Org, error) {
@@ -228,5 +228,5 @@ func (u *IDPProvider) getDefaultIDPConfig(ctx context.Context, idpConfigID strin
if _, existingIDP := existing.GetIDP(idpConfigID); existingIDP != nil {
return existingIDP, nil
}
return nil, errors.ThrowNotFound(nil, "EVENT-49O0f", "Errors.IAM.IdpNotExisting")
return nil, errors.ThrowNotFound(nil, "EVENT-49O0f", "Errors.IDP.NotExisting")
}

View File

@@ -28,7 +28,7 @@ type Commands struct {
iamDomain string
zitadelRoles []authz.RoleMapping
idpConfigSecretCrypto crypto.Crypto
idpConfigSecretCrypto crypto.EncryptionAlgorithm
userPasswordAlg crypto.HashAlgorithm
initializeUserCode crypto.Generator
@@ -39,7 +39,7 @@ type Commands struct {
machineKeySize int
applicationKeySize int
applicationSecretGenerator crypto.Generator
domainVerificationAlg *crypto.AESCrypto
domainVerificationAlg crypto.EncryptionAlgorithm
domainVerificationGenerator crypto.Generator
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
multifactors domain.MultifactorConfigs

View File

@@ -70,6 +70,7 @@ func writeModelToMailText(wm *MailTextWriteModel) *domain.MailText {
Greeting: wm.Greeting,
Text: wm.Text,
ButtonText: wm.ButtonText,
State: wm.State,
}
}

View File

@@ -23,7 +23,7 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
}
addedConfig := NewIAMIDPConfigWriteModel(idpConfigID)
clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
clientSecret, err := crypto.Encrypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
if err != nil {
return nil, err
}
@@ -63,12 +63,15 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
}
func (c *Commands) ChangeDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
if config.IDPConfigID == "" {
return nil, errors.ThrowInvalidArgument(nil, "IAM-4m9gs", "Errors.IDMissing")
}
existingIDP, err := c.iamIDPConfigWriteModelByID(ctx, config.IDPConfigID)
if err != nil {
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting")
}
iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel)
@@ -133,7 +136,7 @@ func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idp
return nil, err
}
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M0xy", "Errors.IAM.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M0xy", "Errors.IDPConfig.NotExisting")
}
iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel)
@@ -168,7 +171,7 @@ func (c *Commands) getIAMIDPConfigByID(ctx context.Context, idpID string) (*doma
return nil, err
}
if !config.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IAM.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.IDPConfig.NotExisting")
}
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
}

View File

@@ -0,0 +1,294 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/idpconfig"
)
func TestCommandSide_AddDefaultIDPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
config *domain.IDPConfig
}
type res struct {
want *domain.IDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "idp config oidc add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
iam.NewIDPOIDCConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
},
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name1", "IAM")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "config1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
ClientSecretString: "secret",
Scopes: []string{"scope"},
IDPDisplayNameMapping: domain.OIDCMappingFieldEmail,
UsernameMapping: domain.OIDCMappingFieldEmail,
},
},
},
res: res{
want: &domain.IDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
IDPConfigID: "config1",
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
State: domain.IDPConfigStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
idpConfigSecretCrypto: tt.fields.secretCrypto,
}
got, err := r.AddDefaultIDPConfig(tt.args.ctx, tt.args.config)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
config *domain.IDPConfig
}
type res struct {
want *domain.IDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "config not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "idp config change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
iam.NewIDPOIDCConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultIDPConfigChangedEvent(context.Background(), "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified),
),
},
uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name1", "IAM")),
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name2", "IAM")),
),
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
},
},
res: res{
want: &domain.IDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
State: domain.IDPConfigStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeDefaultIDPConfig(tt.args.ctx, tt.args.config)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newDefaultIDPConfigChangedEvent(ctx context.Context, configID, oldName, newName string, stylingType domain.IDPConfigStylingType) *iam.IDPConfigChangedEvent {
event, _ := iam.NewIDPConfigChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
configID,
oldName,
[]idpconfig.IDPConfigChanges{
idpconfig.ChangeName(newName),
idpconfig.ChangeStyleType(stylingType),
},
)
return event
}

View File

@@ -7,6 +7,9 @@ import (
)
func (c *Commands) ChangeDefaultIDPOIDCConfig(ctx context.Context, config *domain.OIDCIDPConfig) (*domain.OIDCIDPConfig, error) {
if config.IDPConfigID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-9djf8", "Errors.IDMissing")
}
existingConfig := NewIAMIDPOIDCConfigWriteModel(config.IDPConfigID)
err := c.eventstore.FilterToQueryReducer(ctx, existingConfig)
if err != nil {
@@ -14,7 +17,7 @@ func (c *Commands) ChangeDefaultIDPOIDCConfig(ctx context.Context, config *domai
}
if existingConfig.State == domain.IDPConfigStateRemoved || existingConfig.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-67J9d", "Errors.IAM.IDPConfig.AlreadyExists")
return nil, caos_errs.ThrowNotFound(nil, "IAM-67J9d", "Errors.IAM.IDPConfig.AlreadyExists")
}
iamAgg := IAMAggregateFromWriteModel(&existingConfig.WriteModel)

View File

@@ -0,0 +1,295 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/idpconfig"
)
func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type (
args struct {
ctx context.Context
config *domain.OIDCIDPConfig
}
)
type res struct {
want *domain.OIDCIDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "idp config not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "idp config removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
iam.NewIDPOIDCConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
eventFromEventPusher(
iam.NewIDPConfigRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name",
),
),
),
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
iam.NewIDPOIDCConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
ClientID: "clientid1",
Issuer: "issuer",
Scopes: []string{"scope"},
IDPDisplayNameMapping: domain.OIDCMappingFieldEmail,
UsernameMapping: domain.OIDCMappingFieldEmail,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "idp config oidc add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
iam.NewIDPOIDCConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultIDPOIDCConfigChangedEvent(context.Background(),
"config1",
"clientid-changed",
"issuer-changed",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret-changed"),
},
domain.OIDCMappingFieldPreferredLoginName,
domain.OIDCMappingFieldPreferredLoginName,
[]string{"scope", "scope2"},
),
),
},
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
ClientID: "clientid-changed",
Issuer: "issuer-changed",
ClientSecretString: "secret-changed",
Scopes: []string{"scope", "scope2"},
IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName,
UsernameMapping: domain.OIDCMappingFieldPreferredLoginName,
},
},
res: res{
want: &domain.OIDCIDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
IDPConfigID: "config1",
ClientID: "clientid-changed",
Issuer: "issuer-changed",
Scopes: []string{"scope", "scope2"},
IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName,
UsernameMapping: domain.OIDCMappingFieldPreferredLoginName,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idpConfigSecretCrypto: tt.fields.secretCrypto,
}
got, err := r.ChangeDefaultIDPOIDCConfig(tt.args.ctx, tt.args.config)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newDefaultIDPOIDCConfigChangedEvent(ctx context.Context, configID, clientID, issuer string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *iam.IDPOIDCConfigChangedEvent {
event, _ := iam.NewIDPOIDCConfigChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
configID,
[]idpconfig.OIDCConfigChanges{
idpconfig.ChangeClientID(clientID),
idpconfig.ChangeIssuer(issuer),
idpconfig.ChangeClientSecret(secret),
idpconfig.ChangeIDPDisplayNameMapping(displayMapping),
idpconfig.ChangeUserNameMapping(usernameMapping),
idpconfig.ChangeScopes(scopes),
},
)
return event
}

View File

@@ -85,8 +85,15 @@ func (c *Commands) changeDefaultLoginPolicy(ctx context.Context, iamAgg *eventst
}
func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider) (*domain.IDPProvider, error) {
if !idpProvider.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-9nf88", "Errors.IAM.LoginPolicy.IDP.Invalid")
}
_, err := c.getIAMIDPConfigByID(ctx, idpProvider.IDPConfigID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "IAM-m8fsd", "Errors.IDPConfig.NotExisting")
}
idpModel := NewIAMIdentityProviderWriteModel(idpProvider.IDPConfigID)
err := c.eventstore.FilterToQueryReducer(ctx, idpModel)
err = c.eventstore.FilterToQueryReducer(ctx, idpModel)
if err != nil {
return nil, err
}
@@ -95,7 +102,7 @@ func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpPr
}
iamAgg := IAMAggregateFromWriteModel(&idpModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewIdentityProviderAddedEvent(ctx, iamAgg, idpProvider.IDPConfigID, idpProvider.Type))
pushedEvents, err := c.eventstore.PushEvents(ctx, iam_repo.NewIdentityProviderAddedEvent(ctx, iamAgg, idpProvider.IDPConfigID))
if err != nil {
return nil, err
}
@@ -107,6 +114,9 @@ func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpPr
}
func (c *Commands) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
if !idpProvider.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-66m9s", "Errors.IAM.LoginPolicy.IDP.Invalid")
}
idpModel := NewIAMIdentityProviderWriteModel(idpProvider.IDPConfigID)
err := c.eventstore.FilterToQueryReducer(ctx, idpModel)
if err != nil {
@@ -117,12 +127,7 @@ func (c *Commands) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context,
}
iamAgg := IAMAggregateFromWriteModel(&idpModel.IdentityProviderWriteModel.WriteModel)
events := []eventstore.EventPusher{
iam_repo.NewIdentityProviderRemovedEvent(ctx, iamAgg, idpProvider.IDPConfigID),
}
userEvents := c.removeIDPProviderFromDefaultLoginPolicy(ctx, iamAgg, idpProvider, false, cascadeExternalIDPs...)
events = append(events, userEvents...)
events := c.removeIDPProviderFromDefaultLoginPolicy(ctx, iamAgg, idpProvider, false, cascadeExternalIDPs...)
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
@@ -154,7 +159,10 @@ func (c *Commands) removeIDPProviderFromDefaultLoginPolicy(ctx context.Context,
}
func (c *Commands) AddSecondFactorToDefaultLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType) (domain.SecondFactorType, *domain.ObjectDetails, error) {
secondFactorModel := NewIAMSecondFactorWriteModel()
if !secondFactor.Valid() {
return domain.SecondFactorTypeUnspecified, nil, caos_errs.ThrowInvalidArgument(nil, "IAM-5m9fs", "Errors.IAM.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewIAMSecondFactorWriteModel(secondFactor)
iamAgg := IAMAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
event, err := c.addSecondFactorToDefaultLoginPolicy(ctx, iamAgg, secondFactorModel, secondFactor)
if err != nil {
@@ -185,7 +193,10 @@ func (c *Commands) addSecondFactorToDefaultLoginPolicy(ctx context.Context, iamA
}
func (c *Commands) RemoveSecondFactorFromDefaultLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType) (*domain.ObjectDetails, error) {
secondFactorModel := NewIAMSecondFactorWriteModel()
if !secondFactor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-55n8s", "Errors.IAM.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewIAMSecondFactorWriteModel(secondFactor)
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
if err != nil {
return nil, err
@@ -206,7 +217,10 @@ func (c *Commands) RemoveSecondFactorFromDefaultLoginPolicy(ctx context.Context,
}
func (c *Commands) AddMultiFactorToDefaultLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType) (domain.MultiFactorType, *domain.ObjectDetails, error) {
multiFactorModel := NewIAMMultiFactorWriteModel()
if !multiFactor.Valid() {
return domain.MultiFactorTypeUnspecified, nil, caos_errs.ThrowInvalidArgument(nil, "IAM-5m9fs", "Errors.IAM.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewIAMMultiFactorWriteModel(multiFactor)
iamAgg := IAMAggregateFromWriteModel(&multiFactorModel.MultiFactoryWriteModel.WriteModel)
event, err := c.addMultiFactorToDefaultLoginPolicy(ctx, iamAgg, multiFactorModel, multiFactor)
if err != nil {
@@ -237,7 +251,10 @@ func (c *Commands) addMultiFactorToDefaultLoginPolicy(ctx context.Context, iamAg
}
func (c *Commands) RemoveMultiFactorFromDefaultLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType) (*domain.ObjectDetails, error) {
multiFactorModel := NewIAMMultiFactorWriteModel()
if !multiFactor.Valid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-33m9F", "Errors.IAM.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewIAMMultiFactorWriteModel(multiFactor)
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
if err != nil {
return nil, err

View File

@@ -10,13 +10,14 @@ type IAMSecondFactorWriteModel struct {
SecondFactorWriteModel
}
func NewIAMSecondFactorWriteModel() *IAMSecondFactorWriteModel {
func NewIAMSecondFactorWriteModel(factorType domain.SecondFactorType) *IAMSecondFactorWriteModel {
return &IAMSecondFactorWriteModel{
SecondFactorWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
MFAType: factorType,
},
}
}
@@ -25,15 +26,19 @@ func (wm *IAMSecondFactorWriteModel) AppendEvents(events ...eventstore.EventRead
for _, event := range events {
switch e := event.(type) {
case *iam.LoginPolicySecondFactorAddedEvent:
wm.WriteModel.AppendEvents(&e.SecondFactorAddedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.SecondFactorAddedEvent)
}
case *iam.LoginPolicySecondFactorRemovedEvent:
wm.WriteModel.AppendEvents(&e.SecondFactorRemovedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.SecondFactorRemovedEvent)
}
}
}
}
func (wm *IAMSecondFactorWriteModel) Reduce() error {
return wm.WriteModel.Reduce()
return wm.SecondFactorWriteModel.Reduce()
}
func (wm *IAMSecondFactorWriteModel) Query() *eventstore.SearchQueryBuilder {
@@ -49,13 +54,14 @@ type IAMMultiFactorWriteModel struct {
MultiFactoryWriteModel
}
func NewIAMMultiFactorWriteModel() *IAMMultiFactorWriteModel {
func NewIAMMultiFactorWriteModel(factorType domain.MultiFactorType) *IAMMultiFactorWriteModel {
return &IAMMultiFactorWriteModel{
MultiFactoryWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
ResourceOwner: domain.IAMID,
},
MFAType: factorType,
},
}
}
@@ -64,15 +70,19 @@ func (wm *IAMMultiFactorWriteModel) AppendEvents(events ...eventstore.EventReade
for _, event := range events {
switch e := event.(type) {
case *iam.LoginPolicyMultiFactorAddedEvent:
wm.WriteModel.AppendEvents(&e.MultiFactorAddedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.MultiFactorAddedEvent)
}
case *iam.LoginPolicyMultiFactorRemovedEvent:
wm.WriteModel.AppendEvents(&e.MultiFactorRemovedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.MultiFactorRemovedEvent)
}
}
}
}
func (wm *IAMMultiFactorWriteModel) Reduce() error {
return wm.WriteModel.Reduce()
return wm.MultiFactoryWriteModel.Reduce()
}
func (wm *IAMMultiFactorWriteModel) Query() *eventstore.SearchQueryBuilder {

View File

@@ -9,6 +9,8 @@ import (
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/policy"
"github.com/caos/zitadel/internal/repository/user"
"github.com/stretchr/testify/assert"
"testing"
)
@@ -268,6 +270,917 @@ func TestCommandSide_ChangeDefaultLoginPolicy(t *testing.T) {
}
}
func TestCommandSide_AddIDPProviderDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
provider *domain.IDPProvider
}
type res struct {
want *domain.IDPProvider
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "provider invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "config not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "provider already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
),
),
),
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add provider, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
),
),
),
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1"),
),
},
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
want: &domain.IDPProvider{
ObjectRoot: models.ObjectRoot{
AggregateID: "IAM",
ResourceOwner: "IAM",
},
IDPConfigID: "config1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddIDPProviderToDefaultLoginPolicy(tt.args.ctx, tt.args.provider)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
provider *domain.IDPProvider
cascadeExternalIDPs []*domain.ExternalIDP
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "provider invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "provider not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "provider removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
eventFromEventPusher(
iam.NewIdentityProviderRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove provider, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewIdentityProviderRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1"),
),
},
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
{
name: "remove provider external idp not found, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewIdentityProviderRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1"),
),
},
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
cascadeExternalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
{
name: "remove provider with external idps, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
true,
true,
true,
true,
domain.PasswordlessTypeAllowed,
),
),
eventFromEventPusher(
iam.NewIdentityProviderAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1",
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "", "externaluser1"),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewIdentityProviderRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
"config1"),
),
eventFromEventPusher(
user.NewHumanExternalIDPCascadeRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "externaluser1")),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("config1", "externaluser1")),
),
),
},
args: args{
ctx: context.Background(),
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
cascadeExternalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveIDPProviderFromDefaultLoginPolicy(tt.args.ctx, tt.args.provider, tt.args.cascadeExternalIDPs...)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
factor domain.SecondFactorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "factor invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "factor already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicySecondFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP,
),
),
),
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeOTP,
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add factor, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLoginPolicySecondFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP),
),
},
),
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeOTP,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
_, got, err := r.AddSecondFactorToDefaultLoginPolicy(tt.args.ctx, tt.args.factor)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
factor domain.SecondFactorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "factor invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "factor not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeOTP,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "factor removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicySecondFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP,
),
),
eventFromEventPusher(
iam.NewLoginPolicySecondFactorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP,
),
),
),
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeOTP,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "add factor, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicySecondFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLoginPolicySecondFactorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.SecondFactorTypeOTP),
),
},
),
),
},
args: args{
ctx: context.Background(),
factor: domain.SecondFactorTypeOTP,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveSecondFactorFromDefaultLoginPolicy(tt.args.ctx, tt.args.factor)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_AddMultiFactorDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
factor domain.MultiFactorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "factor invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "factor already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN,
),
),
),
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeU2FWithPIN,
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add factor, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN),
),
},
),
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeU2FWithPIN,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
_, got, err := r.AddMultiFactorToDefaultLoginPolicy(tt.args.ctx, tt.args.factor)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveMultiFactorDefaultLoginPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
factor domain.MultiFactorType
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "factor invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeUnspecified,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "factor not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeU2FWithPIN,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "factor removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN,
),
),
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN,
),
),
),
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeU2FWithPIN,
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "add factor, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorAddedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
iam.NewLoginPolicyMultiFactorRemovedEvent(context.Background(),
&iam.NewAggregate().Aggregate,
domain.MultiFactorTypeU2FWithPIN),
),
},
),
),
},
args: args{
ctx: context.Background(),
factor: domain.MultiFactorTypeU2FWithPIN,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "IAM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveMultiFactorFromDefaultLoginPolicy(tt.args.ctx, tt.args.factor)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newDefaultLoginPolicyChangedEvent(ctx context.Context, allowRegister, allowUsernamePassword, allowExternalIDP, forceMFA bool, passwordlessType domain.PasswordlessType) *iam.LoginPolicyChangedEvent {
event, _ := iam.NewLoginPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,

View File

@@ -44,7 +44,7 @@ func (c *Commands) ChangeDefaultOrgIAMPolicy(ctx context.Context, policy *domain
if err != nil {
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
if !existingPolicy.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "IAM-0Pl0d", "Errors.IAM.OrgIAMPolicy.NotFound")
}
@@ -70,6 +70,9 @@ func (c *Commands) getDefaultOrgIAMPolicy(ctx context.Context) (*domain.OrgIAMPo
if err != nil {
return nil, err
}
if !policyWriteModel.State.Exists() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-3n8fs", "Errors.IAM.PasswordComplexityPolicy.NotFound")
}
policy := writeModelToOrgIAMPolicy(policyWriteModel)
policy.Default = true
return policy, nil

View File

@@ -15,6 +15,9 @@ func (c *Commands) getDefaultPasswordComplexityPolicy(ctx context.Context) (*dom
if err != nil {
return nil, err
}
if !policyWriteModel.State.Exists() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-M0gsf", "Errors.IAM.OrgIAMPolicy.NotFound")
}
policy := writeModelToPasswordComplexityPolicy(&policyWriteModel.PasswordComplexityPolicyWriteModel)
policy.Default = true
return policy, nil

View File

@@ -147,6 +147,12 @@ func eventFromEventPusher(event eventstore.EventPusher) *repository.Event {
}
}
func eventFromEventPusherWithCreationDateNow(event eventstore.EventPusher) *repository.Event {
e := eventFromEventPusher(event)
e.CreationDate = time.Now()
return e
}
func uniqueConstraintsFromEventConstraint(constraint *eventstore.EventUniqueConstraint) *repository.UniqueConstraint {
return &repository.UniqueConstraint{
UniqueType: constraint.UniqueType,

View File

@@ -13,6 +13,9 @@ import (
)
func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.OrgDomain, error) {
if !orgDomain.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain)
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
events, err := c.addOrgDomain(ctx, orgAgg, domainWriteModel, orgDomain)
@@ -31,19 +34,19 @@ func (c *Commands) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain
}
func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *domain.OrgDomain) (token, url string, err error) {
if orgDomain == nil || !orgDomain.IsValid() {
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
checkType, ok := orgDomain.ValidationType.CheckType()
if !ok {
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
return "", "", caos_errs.ThrowInvalidArgument(nil, "ORG-Gsw31", "Errors.Org.DomainVerificationTypeInvalid")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return "", "", err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
return "", "", caos_errs.ThrowNotFound(nil, "ORG-AGD31", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Verified {
return "", "", caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
@@ -69,15 +72,15 @@ func (c *Commands) GenerateOrgDomainValidation(ctx context.Context, orgDomain *d
}
func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain, claimedUserIDs ...string) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-R24hb", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
return nil, caos_errs.ThrowNotFound(nil, "ORG-Sjdi3", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Verified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-HGw21", "Errors.Org.DomainAlreadyVerified")
@@ -122,15 +125,15 @@ func (c *Commands) ValidateOrgDomain(ctx context.Context, orgDomain *domain.OrgD
}
func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SsDG2", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
}
if !domainWriteModel.Verified {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Ggd32", "Errors.Org.DomainNotVerified")
@@ -148,21 +151,21 @@ func (c *Commands) SetPrimaryOrgDomain(ctx context.Context, orgDomain *domain.Or
}
func (c *Commands) RemoveOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.ObjectDetails, error) {
if orgDomain == nil || !orgDomain.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
if orgDomain == nil || !orgDomain.IsValid() || orgDomain.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SJsK3", "Errors.Org.InvalidDomain")
}
domainWriteModel, err := c.getOrgDomainWriteModel(ctx, orgDomain.AggregateID, orgDomain.Domain)
if err != nil {
return nil, err
}
if domainWriteModel.State != domain.OrgDomainStateActive {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
return nil, caos_errs.ThrowNotFound(nil, "ORG-GDfA3", "Errors.Org.DomainNotOnOrg")
}
if domainWriteModel.Primary {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-Sjdi3", "Errors.Org.PrimaryDomainNotDeletable")
}
orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain))
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewDomainRemovedEvent(ctx, orgAgg, orgDomain.Domain, domainWriteModel.Verified))
if err != nil {
return nil, err
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,10 @@ import (
org_repo "github.com/caos/zitadel/internal/repository/org"
)
func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, resourceOwner string) (*domain.IDPConfig, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-0j8gs", "Errors.ResourceOwnerMissing")
}
if config.OIDCConfig == nil {
return nil, errors.ThrowInvalidArgument(nil, "Org-eUpQU", "Errors.idp.config.notset")
}
@@ -21,7 +24,7 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig) (
if err != nil {
return nil, err
}
addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, config.AggregateID)
addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, resourceOwner)
clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
if err != nil {
@@ -60,7 +63,10 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig) (
return writeModelToIDPConfig(&addedConfig.IDPConfigWriteModel), nil
}
func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
func (c *Commands) ChangeIDPConfig(ctx context.Context, config *domain.IDPConfig, resourceOwner string) (*domain.IDPConfig, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Gh8ds", "Errors.ResourceOwnerMissing")
}
existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, config.IDPConfigID, config.AggregateID)
if err != nil {
return nil, err
@@ -149,7 +155,7 @@ func (c *Commands) getOrgIDPConfigByID(ctx context.Context, idpID, orgID string)
return nil, err
}
if !config.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "IAM-4M9so", "Errors.Org.IDPConfig.NotExisting")
return nil, caos_errs.ThrowNotFound(nil, "ORG-4M9so", "Errors.Org.IDPConfig.NotExisting")
}
return writeModelToIDPConfig(&config.IDPConfigWriteModel), nil
}

View File

@@ -0,0 +1,343 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/idpconfig"
"github.com/caos/zitadel/internal/repository/org"
)
func TestCommandSide_AddIDPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
config *domain.IDPConfig
resourceOwner string
}
type res struct {
want *domain.IDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "resourceowner missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
ClientSecretString: "secret",
Scopes: []string{"scope"},
IDPDisplayNameMapping: domain.OIDCMappingFieldEmail,
UsernameMapping: domain.OIDCMappingFieldEmail,
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.IDPConfig{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "idp config oidc add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
},
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name1", "org1")),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "config1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.IDPConfig{
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
OIDCConfig: &domain.OIDCIDPConfig{
ClientID: "clientid1",
Issuer: "issuer",
ClientSecretString: "secret",
Scopes: []string{"scope"},
IDPDisplayNameMapping: domain.OIDCMappingFieldEmail,
UsernameMapping: domain.OIDCMappingFieldEmail,
},
},
},
res: res{
want: &domain.IDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
IDPConfigID: "config1",
Name: "name1",
StylingType: domain.IDPConfigStylingTypeGoogle,
State: domain.IDPConfigStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
idpConfigSecretCrypto: tt.fields.secretCrypto,
}
got, err := r.AddIDPConfig(tt.args.ctx, tt.args.config, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeIDPConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
resourceOwner string
config *domain.IDPConfig
}
type res struct {
want *domain.IDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing resourceowner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.IDPConfig{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "config not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.IDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "idp config change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newIDPConfigChangedEvent(context.Background(), "org1", "config1", "name1", "name2", domain.IDPConfigStylingTypeUnspecified),
),
},
uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name1", "org1")),
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name2", "org1")),
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.IDPConfig{
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
},
},
res: res{
want: &domain.IDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
IDPConfigID: "config1",
Name: "name2",
StylingType: domain.IDPConfigStylingTypeUnspecified,
State: domain.IDPConfigStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeIDPConfig(tt.args.ctx, tt.args.config, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newIDPConfigChangedEvent(ctx context.Context, orgID, configID, oldName, newName string, stylingType domain.IDPConfigStylingType) *org.IDPConfigChangedEvent {
event, _ := org.NewIDPConfigChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
configID,
oldName,
[]idpconfig.IDPConfigChanges{
idpconfig.ChangeName(newName),
idpconfig.ChangeStyleType(stylingType),
},
)
return event
}

View File

@@ -6,15 +6,21 @@ import (
caos_errs "github.com/caos/zitadel/internal/errors"
)
func (c *Commands) ChangeIDPOIDCConfig(ctx context.Context, config *domain.OIDCIDPConfig) (*domain.OIDCIDPConfig, error) {
existingConfig := NewOrgIDPOIDCConfigWriteModel(config.IDPConfigID, config.AggregateID)
func (c *Commands) ChangeIDPOIDCConfig(ctx context.Context, config *domain.OIDCIDPConfig, resourceOwner string) (*domain.OIDCIDPConfig, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4n8f2", "Errors.ResourceOwnerMissing")
}
if config.IDPConfigID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-66Qwj", "Errors.IDMissing")
}
existingConfig := NewOrgIDPOIDCConfigWriteModel(config.IDPConfigID, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingConfig)
if err != nil {
return nil, err
}
if existingConfig.State == domain.IDPConfigStateRemoved || existingConfig.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-67J9d", "Errors.Org.IDPConfig.AlreadyExists")
return nil, caos_errs.ThrowNotFound(nil, "Org-67J9d", "Errors.Org.IDPConfig.AlreadyExists")
}
orgAgg := OrgAggregateFromWriteModel(&existingConfig.WriteModel)

View File

@@ -115,7 +115,7 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent(
if userNameMapping.Valid() && wm.UserNameMapping != userNameMapping {
changes = append(changes, idpconfig.ChangeUserNameMapping(userNameMapping))
}
if reflect.DeepEqual(wm.Scopes, scopes) {
if !reflect.DeepEqual(wm.Scopes, scopes) {
changes = append(changes, idpconfig.ChangeScopes(scopes))
}
if len(changes) == 0 {

View File

@@ -0,0 +1,319 @@
package command
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/idpconfig"
"github.com/caos/zitadel/internal/repository/org"
)
func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type (
args struct {
ctx context.Context
config *domain.OIDCIDPConfig
resourceOwner string
}
)
type res struct {
want *domain.OIDCIDPConfig
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "resourceowner missing, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid config, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "idp config not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "idp config removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
eventFromEventPusher(
org.NewIDPConfigRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name",
),
),
),
),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
ClientID: "clientid1",
Issuer: "issuer",
Scopes: []string{"scope"},
IDPDisplayNameMapping: domain.OIDCMappingFieldEmail,
UsernameMapping: domain.OIDCMappingFieldEmail,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "idp config oidc add, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name1",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeGoogle,
),
),
eventFromEventPusher(
org.NewIDPOIDCConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"clientid1",
"config1",
"issuer",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret"),
},
domain.OIDCMappingFieldEmail,
domain.OIDCMappingFieldEmail,
"scope",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newIDPOIDCConfigChangedEvent(context.Background(),
"org1",
"config1",
"clientid-changed",
"issuer-changed",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("secret-changed"),
},
domain.OIDCMappingFieldPreferredLoginName,
domain.OIDCMappingFieldPreferredLoginName,
[]string{"scope", "scope2"},
),
),
},
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
config: &domain.OIDCIDPConfig{
IDPConfigID: "config1",
ClientID: "clientid-changed",
Issuer: "issuer-changed",
ClientSecretString: "secret-changed",
Scopes: []string{"scope", "scope2"},
IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName,
UsernameMapping: domain.OIDCMappingFieldPreferredLoginName,
},
resourceOwner: "org1",
},
res: res{
want: &domain.OIDCIDPConfig{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
IDPConfigID: "config1",
ClientID: "clientid-changed",
Issuer: "issuer-changed",
Scopes: []string{"scope", "scope2"},
IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName,
UsernameMapping: domain.OIDCMappingFieldPreferredLoginName,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idpConfigSecretCrypto: tt.fields.secretCrypto,
}
got, err := r.ChangeIDPOIDCConfig(tt.args.ctx, tt.args.config, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newIDPOIDCConfigChangedEvent(ctx context.Context, orgID, configID, clientID, issuer string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *org.IDPOIDCConfigChangedEvent {
event, _ := org.NewIDPOIDCConfigChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
configID,
[]idpconfig.OIDCConfigChanges{
idpconfig.ChangeClientID(clientID),
idpconfig.ChangeIssuer(issuer),
idpconfig.ChangeClientSecret(secret),
idpconfig.ChangeIDPDisplayNameMapping(displayMapping),
idpconfig.ChangeUserNameMapping(usernameMapping),
idpconfig.ChangeScopes(scopes),
},
)
return event
}

View File

@@ -9,6 +9,12 @@ import (
)
func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, policy *domain.LabelPolicy) (*domain.LabelPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Md9sf", "Errors.Org.LabelPolicy.Invalid")
}
addedPolicy := NewOrgLabelPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -31,6 +37,12 @@ func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, pol
}
func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string, policy *domain.LabelPolicy) (*domain.LabelPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3N9fs", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-dM9fs", "Errors.Org.LabelPolicy.Invalid")
}
existingPolicy := NewOrgLabelPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
@@ -58,6 +70,9 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
}
func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) error {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgLabelPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,429 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddLabelPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.LabelPolicy
}
type res struct {
want *domain.LabelPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
want: &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddLabelPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.LabelPolicy
}
type res struct {
want *domain.LabelPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newLabelPolicyChangedEvent(context.Background(), "org1", "primary-color-change", "secondary-color-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
},
},
res: res{
want: &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeLabelPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveLabelPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "labelpolicy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewLabelPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewLabelPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveLabelPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, secondaryColor string) *org.LabelPolicyChangedEvent {
event, _ := org.NewLabelPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.LabelPolicyChanges{
policy.ChangePrimaryColor(primaryColor),
policy.ChangeSecondaryColor(secondaryColor),
},
)
return event
}

View File

@@ -10,6 +10,9 @@ import (
)
func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Fn8ds", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -41,6 +44,9 @@ func (c *Commands) AddLoginPolicy(ctx context.Context, resourceOwner string, pol
}
func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string, policy *domain.LoginPolicy) (*domain.LoginPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgLoginPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
@@ -67,6 +73,9 @@ func (c *Commands) ChangeLoginPolicy(ctx context.Context, resourceOwner string,
}
func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-55Mg9", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgLoginPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
@@ -88,8 +97,23 @@ func (c *Commands) RemoveLoginPolicy(ctx context.Context, orgID string) (*domain
}
func (c *Commands) AddIDPProviderToLoginPolicy(ctx context.Context, resourceOwner string, idpProvider *domain.IDPProvider) (*domain.IDPProvider, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M0fs9", "Errors.ResourceOwnerMissing")
}
if !idpProvider.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-9nf88", "Errors.Org.LoginPolicy.IDP.")
}
var err error
if idpProvider.Type == domain.IdentityProviderTypeOrg {
_, err = c.getOrgIDPConfigByID(ctx, idpProvider.IDPConfigID, resourceOwner)
} else {
_, err = c.getIAMIDPConfigByID(ctx, idpProvider.IDPConfigID)
}
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "Org-3N9fs", "Errors.IDPConfig.NotExisting")
}
idpModel := NewOrgIdentityProviderWriteModel(resourceOwner, idpProvider.IDPConfigID)
err := c.eventstore.FilterToQueryReducer(ctx, idpModel)
err = c.eventstore.FilterToQueryReducer(ctx, idpModel)
if err != nil {
return nil, err
}
@@ -110,6 +134,12 @@ func (c *Commands) AddIDPProviderToLoginPolicy(ctx context.Context, resourceOwne
}
func (c *Commands) RemoveIDPProviderFromLoginPolicy(ctx context.Context, resourceOwner string, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M0fs9", "Errors.ResourceOwnerMissing")
}
if !idpProvider.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-66m9s", "Errors.Org.LoginPolicy.IDP.Invalid")
}
idpModel := NewOrgIdentityProviderWriteModel(resourceOwner, idpProvider.IDPConfigID)
err := c.eventstore.FilterToQueryReducer(ctx, idpModel)
if err != nil {
@@ -153,7 +183,13 @@ func (c *Commands) removeIDPProviderFromLoginPolicy(ctx context.Context, orgAgg
}
func (c *Commands) AddSecondFactorToLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType, orgID string) (domain.SecondFactorType, error) {
secondFactorModel := NewOrgSecondFactorWriteModel(orgID)
if orgID == "" {
return domain.SecondFactorTypeUnspecified, caos_errs.ThrowInvalidArgument(nil, "Org-M0fs9", "Errors.ResourceOwnerMissing")
}
if !secondFactor.Valid() {
return domain.SecondFactorTypeUnspecified, caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewOrgSecondFactorWriteModel(orgID, secondFactor)
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
if err != nil {
return domain.SecondFactorTypeUnspecified, err
@@ -173,7 +209,13 @@ func (c *Commands) AddSecondFactorToLoginPolicy(ctx context.Context, secondFacto
}
func (c *Commands) RemoveSecondFactorFromLoginPolicy(ctx context.Context, secondFactor domain.SecondFactorType, orgID string) error {
secondFactorModel := NewOrgSecondFactorWriteModel(orgID)
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-fM0gs", "Errors.ResourceOwnerMissing")
}
if !secondFactor.Valid() {
return caos_errs.ThrowInvalidArgument(nil, "Org-55n8s", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
secondFactorModel := NewOrgSecondFactorWriteModel(orgID, secondFactor)
err := c.eventstore.FilterToQueryReducer(ctx, secondFactorModel)
if err != nil {
return err
@@ -188,7 +230,13 @@ func (c *Commands) RemoveSecondFactorFromLoginPolicy(ctx context.Context, second
}
func (c *Commands) AddMultiFactorToLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType, orgID string) (domain.MultiFactorType, error) {
multiFactorModel := NewOrgMultiFactorWriteModel(orgID)
if orgID == "" {
return domain.MultiFactorTypeUnspecified, caos_errs.ThrowInvalidArgument(nil, "Org-M0fsf", "Errors.ResourceOwnerMissing")
}
if !multiFactor.Valid() {
return domain.MultiFactorTypeUnspecified, caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewOrgMultiFactorWriteModel(orgID, multiFactor)
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
if err != nil {
return domain.MultiFactorTypeUnspecified, err
@@ -207,7 +255,13 @@ func (c *Commands) AddMultiFactorToLoginPolicy(ctx context.Context, multiFactor
}
func (c *Commands) RemoveMultiFactorFromLoginPolicy(ctx context.Context, multiFactor domain.MultiFactorType, orgID string) error {
multiFactorModel := NewOrgMultiFactorWriteModel(orgID)
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-M0fsf", "Errors.ResourceOwnerMissing")
}
if !multiFactor.Valid() {
return caos_errs.ThrowInvalidArgument(nil, "Org-5m9fs", "Errors.Org.LoginPolicy.MFA.Unspecified")
}
multiFactorModel := NewOrgMultiFactorWriteModel(orgID, multiFactor)
err := c.eventstore.FilterToQueryReducer(ctx, multiFactorModel)
if err != nil {
return err

View File

@@ -1,6 +1,7 @@
package command
import (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
)
@@ -9,13 +10,14 @@ type OrgSecondFactorWriteModel struct {
SecondFactorWriteModel
}
func NewOrgSecondFactorWriteModel(orgID string) *OrgSecondFactorWriteModel {
func NewOrgSecondFactorWriteModel(orgID string, factorType domain.SecondFactorType) *OrgSecondFactorWriteModel {
return &OrgSecondFactorWriteModel{
SecondFactorWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
MFAType: factorType,
},
}
}
@@ -24,15 +26,19 @@ func (wm *OrgSecondFactorWriteModel) AppendEvents(events ...eventstore.EventRead
for _, event := range events {
switch e := event.(type) {
case *org.LoginPolicySecondFactorAddedEvent:
wm.WriteModel.AppendEvents(&e.SecondFactorAddedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.SecondFactorAddedEvent)
}
case *org.LoginPolicySecondFactorRemovedEvent:
wm.WriteModel.AppendEvents(&e.SecondFactorRemovedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.SecondFactorRemovedEvent)
}
}
}
}
func (wm *OrgSecondFactorWriteModel) Reduce() error {
return wm.WriteModel.Reduce()
return wm.SecondFactorWriteModel.Reduce()
}
func (wm *OrgSecondFactorWriteModel) Query() *eventstore.SearchQueryBuilder {
@@ -48,13 +54,14 @@ type OrgMultiFactorWriteModel struct {
MultiFactoryWriteModel
}
func NewOrgMultiFactorWriteModel(orgID string) *OrgMultiFactorWriteModel {
func NewOrgMultiFactorWriteModel(orgID string, factorType domain.MultiFactorType) *OrgMultiFactorWriteModel {
return &OrgMultiFactorWriteModel{
MultiFactoryWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
MFAType: factorType,
},
}
}
@@ -63,15 +70,19 @@ func (wm *OrgMultiFactorWriteModel) AppendEvents(events ...eventstore.EventReade
for _, event := range events {
switch e := event.(type) {
case *org.LoginPolicyMultiFactorAddedEvent:
wm.WriteModel.AppendEvents(&e.MultiFactorAddedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.MultiFactorAddedEvent)
}
case *org.LoginPolicyMultiFactorRemovedEvent:
wm.WriteModel.AppendEvents(&e.MultiFactorRemovedEvent)
if wm.MFAType == e.MFAType {
wm.WriteModel.AppendEvents(&e.MultiFactorRemovedEvent)
}
}
}
}
func (wm *OrgMultiFactorWriteModel) Reduce() error {
return wm.WriteModel.Reduce()
return wm.MultiFactoryWriteModel.Reduce()
}
func (wm *OrgMultiFactorWriteModel) Query() *eventstore.SearchQueryBuilder {

View File

@@ -2,7 +2,7 @@ package command
import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
)
type OrgIdentityProviderWriteModel struct {
@@ -24,11 +24,16 @@ func NewOrgIdentityProviderWriteModel(orgID, idpConfigID string) *OrgIdentityPro
func (wm *OrgIdentityProviderWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *iam.IdentityProviderAddedEvent:
case *org.IdentityProviderAddedEvent:
if e.IDPConfigID != wm.IDPConfigID {
continue
}
wm.IdentityProviderWriteModel.AppendEvents(&e.IdentityProviderAddedEvent)
case *org.IdentityProviderRemovedEvent:
if e.IDPConfigID != wm.IDPConfigID {
continue
}
wm.IdentityProviderWriteModel.AppendEvents(&e.IdentityProviderRemovedEvent)
}
}
}
@@ -38,7 +43,10 @@ func (wm *OrgIdentityProviderWriteModel) Reduce() error {
}
func (wm *OrgIdentityProviderWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType).
AggregateIDs(wm.AggregateID).
ResourceOwner(wm.ResourceOwner)
ResourceOwner(wm.ResourceOwner).
EventTypes(
org.LoginPolicyIDPProviderAddedEventType,
org.LoginPolicyIDPProviderRemovedEventType)
}

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,9 @@ import (
)
func (c *Commands) AddMailTemplate(ctx context.Context, resourceOwner string, policy *domain.MailTemplate) (*domain.MailTemplate, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M8dfs", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-3m9fs", "Errors.Org.MailTemplate.Invalid")
}
@@ -34,6 +37,9 @@ func (c *Commands) AddMailTemplate(ctx context.Context, resourceOwner string, po
}
func (c *Commands) ChangeMailTemplate(ctx context.Context, resourceOwner string, policy *domain.MailTemplate) (*domain.MailTemplate, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M9fFs", "Errors.ResourceOwnerMissing")
}
if !policy.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-9f9ds", "Errors.Org.MailTemplate.Invalid")
}
@@ -64,6 +70,9 @@ func (c *Commands) ChangeMailTemplate(ctx context.Context, resourceOwner string,
}
func (c *Commands) RemoveMailTemplate(ctx context.Context, orgID string) error {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-5Jgis", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgMailTemplateWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,381 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddMailTemplate(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailTemplate
}
type res struct {
want *domain.MailTemplate
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTemplateAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
[]byte("template"),
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTemplateAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
[]byte("template"),
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
want: &domain.MailTemplate{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
Template: []byte("template"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddMailTemplate(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeMailTemplate(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailTemplate
}
type res struct {
want *domain.MailTemplate
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTemplateAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
[]byte("template"),
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailTemplate{
Template: []byte("template"),
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTemplateAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
[]byte("template"),
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newMailTemplateChangedEvent(context.Background(), "org1", "template2"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailTemplate{
Template: []byte("template2"),
},
},
res: res{
want: &domain.MailTemplate{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
Template: []byte("template2"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeMailTemplate(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveMailTemplate(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTemplateAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
[]byte("template"),
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTemplateRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveMailTemplate(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newMailTemplateChangedEvent(ctx context.Context, orgID string, template string) *org.MailTemplateChangedEvent {
event, _ := org.NewMailTemplateChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.MailTemplateChanges{
policy.ChangeTemplate([]byte(template)),
},
)
return event
}

View File

@@ -9,8 +9,11 @@ import (
)
func (c *Commands) AddMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-MFiig", "Errors.ResourceOwnerMissing")
}
if !mailText.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4778u", "Errors.Org.MailText.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4778u", "Errors.Org.MailText.Invalid")
}
addedPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
@@ -47,8 +50,11 @@ func (c *Commands) AddMailText(ctx context.Context, resourceOwner string, mailTe
}
func (c *Commands) ChangeMailText(ctx context.Context, resourceOwner string, mailText *domain.MailText) (*domain.MailText, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-NFus3", "Errors.ResourceOwnerMissing")
}
if !mailText.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-3m9fs", "Errors.Org.MailText.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3m9fs", "Errors.Org.MailText.Invalid")
}
existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailText.MailTextType, mailText.Language)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
@@ -88,6 +94,12 @@ func (c *Commands) ChangeMailText(ctx context.Context, resourceOwner string, mai
}
func (c *Commands) RemoveMailText(ctx context.Context, resourceOwner, mailTextType, language string) error {
if resourceOwner == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-2N7fd", "Errors.ResourceOwnerMissing")
}
if mailTextType == "" || language == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-N8fsf", "Errors.Org.MailText.Invalid")
}
existingPolicy := NewOrgMailTextWriteModel(resourceOwner, mailTextType, language)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,563 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "mail text already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
},
uniqueConstraintsFromEventConstraint(policy.NewAddMailTextUniqueConstraint("org1", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.MailText
}
type res struct {
want *domain.MailText
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mailtext invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.MailText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title",
PreHeader: "pre-header",
Subject: "subject",
Greeting: "greeting",
Text: "text",
ButtonText: "button-text",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newMailTextChangedEvent(
context.Background(),
"org1",
"mail-text-type",
"de",
"title-change",
"pre-header-change",
"subject-change",
"greeting-change",
"text-change",
"button-text-change"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.MailText{
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
},
},
res: res{
want: &domain.MailText{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MailTextType: "mail-text-type",
Language: "de",
Title: "title-change",
PreHeader: "pre-header-change",
Subject: "subject-change",
Greeting: "greeting-change",
Text: "text-change",
ButtonText: "button-text-change",
State: domain.PolicyStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeMailText(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveMailText(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
mailTextType string
language string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
mailTextType: "mail-text-type",
language: "de",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewMailTextAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de",
"title",
"pre-header",
"subject",
"greeting",
"text",
"button-text",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewMailTextRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"mail-text-type",
"de"),
),
},
uniqueConstraintsFromEventConstraint(policy.NewRemoveMailTextUniqueConstraint("org1", "mail-text-type", "de")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
mailTextType: "mail-text-type",
language: "de",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveMailText(tt.args.ctx, tt.args.orgID, tt.args.mailTextType, tt.args.language)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newMailTextChangedEvent(ctx context.Context, orgID, mailTextType, language, title, preHeader, subject, greeting, text, buttonText string) *org.MailTextChangedEvent {
event, _ := org.NewMailTextChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
mailTextType,
language,
[]policy.MailTextChanges{
policy.ChangeTitle(title),
policy.ChangePreHeader(preHeader),
policy.ChangeSubject(subject),
policy.ChangeGreeting(greeting),
policy.ChangeText(text),
policy.ChangeButtonText(buttonText),
},
)
return event
}

View File

@@ -10,6 +10,9 @@ import (
)
func (c *Commands) AddOrgIAMPolicy(ctx context.Context, resourceOwner string, policy *domain.OrgIAMPolicy) (*domain.OrgIAMPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-4Jfsf", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewORGOrgIAMPolicyWriteModel(resourceOwner)
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.PolicyOrgIAMWriteModel.WriteModel)
event, err := c.addOrgIAMPolicy(ctx, orgAgg, addedPolicy, policy)
@@ -39,6 +42,9 @@ func (c *Commands) addOrgIAMPolicy(ctx context.Context, orgAgg *eventstore.Aggre
}
func (c *Commands) ChangeOrgIAMPolicy(ctx context.Context, resourceOwner string, policy *domain.OrgIAMPolicy) (*domain.OrgIAMPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-5H8fs", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgIAMPolicyWriteModelByID(ctx, resourceOwner)
if err != nil {
return nil, err
@@ -65,12 +71,15 @@ func (c *Commands) ChangeOrgIAMPolicy(ctx context.Context, resourceOwner string,
}
func (c *Commands) RemoveOrgIAMPolicy(ctx context.Context, orgID string) error {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-3H8fs", "Errors.ResourceOwnerMissing")
}
existingPolicy, err := c.orgIAMPolicyWriteModelByID(ctx, orgID)
if err != nil {
return err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.OrgIAMPolicy.NotFound")
return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.OrgIAM.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PolicyOrgIAMWriteModel.WriteModel)

View File

@@ -0,0 +1,381 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddOrgIAMPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.OrgIAMPolicy
}
type res struct {
want *domain.OrgIAMPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
want: &domain.OrgIAMPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
UserLoginMustBeDomain: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddOrgIAMPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeOrgIAMPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.OrgIAMPolicy
}
type res struct {
want *domain.OrgIAMPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newOrgIAMPolicyChangedEvent(context.Background(), "org1", false),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.OrgIAMPolicy{
UserLoginMustBeDomain: false,
},
},
res: res{
want: &domain.OrgIAMPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
UserLoginMustBeDomain: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeOrgIAMPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewOrgIAMPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveOrgIAMPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newOrgIAMPolicyChangedEvent(ctx context.Context, orgID string, userLoginMustBeDomain bool) *org.OrgIAMPolicyChangedEvent {
event, _ := org.NewOrgIAMPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.OrgIAMPolicyChanges{
policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain),
},
)
return event
}

View File

@@ -9,6 +9,9 @@ import (
)
func (c *Commands) AddPasswordAgePolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordAgePolicy) (*domain.PasswordAgePolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M9fsd", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewOrgPasswordAgePolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -31,6 +34,9 @@ func (c *Commands) AddPasswordAgePolicy(ctx context.Context, resourceOwner strin
}
func (c *Commands) ChangePasswordAgePolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordAgePolicy) (*domain.PasswordAgePolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-57tGs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordAgePolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
@@ -58,6 +64,9 @@ func (c *Commands) ChangePasswordAgePolicy(ctx context.Context, resourceOwner st
}
func (c *Commands) RemovePasswordAgePolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2N8fs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordAgePolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,399 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddPasswordAgePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordAgePolicy
}
type res struct {
want *domain.PasswordAgePolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordAgePolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
365,
10,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordAgePolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
365,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
want: &domain.PasswordAgePolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddPasswordAgePolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangePasswordAgePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordAgePolicy
}
type res struct {
want *domain.PasswordAgePolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordAgePolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
365,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 365,
ExpireWarnDays: 10,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordAgePolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
365,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newPasswordAgePolicyChangedEvent(context.Background(), "org1", 150, 5),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordAgePolicy{
MaxAgeDays: 150,
ExpireWarnDays: 5,
},
},
res: res{
want: &domain.PasswordAgePolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAgeDays: 150,
ExpireWarnDays: 5,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangePasswordAgePolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemovePasswordAgePolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordAgePolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
365,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordAgePolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemovePasswordAgePolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newPasswordAgePolicyChangedEvent(ctx context.Context, orgID string, maxAgeDays, expireWarnDays uint64) *org.PasswordAgePolicyChangedEvent {
event, _ := org.NewPasswordAgePolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.PasswordAgePolicyChanges{
policy.ChangeMaxAgeDays(maxAgeDays),
policy.ChangeExpireWarnDays(expireWarnDays),
},
)
return event
}

View File

@@ -21,6 +21,9 @@ func (c *Commands) getOrgPasswordComplexityPolicy(ctx context.Context, orgID str
}
func (c *Commands) AddPasswordComplexityPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordComplexityPolicy) (*domain.PasswordComplexityPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-7ufEs", "Errors.ResourceOwnerMissing")
}
if err := policy.IsValid(); err != nil {
return nil, err
}
@@ -55,6 +58,9 @@ func (c *Commands) AddPasswordComplexityPolicy(ctx context.Context, resourceOwne
}
func (c *Commands) ChangePasswordComplexityPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordComplexityPolicy) (*domain.PasswordComplexityPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3J8fs", "Errors.ResourceOwnerMissing")
}
if err := policy.IsValid(); err != nil {
return nil, err
}
@@ -86,6 +92,9 @@ func (c *Commands) ChangePasswordComplexityPolicy(ctx context.Context, resourceO
}
func (c *Commands) RemovePasswordComplexityPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-J8fsf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordComplexityPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,429 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddPasswordComplexityPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordComplexityPolicy
}
type res struct {
want *domain.PasswordComplexityPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 0,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
8,
true, true, true, true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
8,
true, true, true, true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
want: &domain.PasswordComplexityPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddPasswordComplexityPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangePasswordComplexityPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordComplexityPolicy
}
type res struct {
want *domain.PasswordComplexityPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordComplexityPolicy{
MinLength: 0,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
8,
true, true, true, true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordComplexityPolicy{
MinLength: 8,
HasUppercase: true,
HasLowercase: true,
HasNumber: true,
HasSymbol: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
8,
true, true, true, true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newPasswordComplexityPolicyChangedEvent(context.Background(), "org1", 10, false, false, false, false),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordComplexityPolicy{
MinLength: 10,
HasUppercase: false,
HasLowercase: false,
HasNumber: false,
HasSymbol: false,
},
},
res: res{
want: &domain.PasswordComplexityPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MinLength: 10,
HasUppercase: false,
HasLowercase: false,
HasNumber: false,
HasSymbol: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangePasswordComplexityPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemovePasswordComplexityPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
8,
true, true, true, true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordComplexityPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemovePasswordComplexityPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newPasswordComplexityPolicyChangedEvent(ctx context.Context, orgID string, minLength uint64, hasUpper, hasLower, hasNumber, hasSymbol bool) *org.PasswordComplexityPolicyChangedEvent {
event, _ := org.NewPasswordComplexityPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.PasswordComplexityPolicyChanges{
policy.ChangeMinLength(minLength),
policy.ChangeHasUppercase(hasUpper),
policy.ChangeHasLowercase(hasLower),
policy.ChangeHasSymbol(hasNumber),
policy.ChangeHasNumber(hasSymbol),
},
)
return event
}

View File

@@ -8,6 +8,9 @@ import (
)
func (c *Commands) AddPasswordLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-8fJif", "Errors.ResourceOwnerMissing")
}
addedPolicy := NewOrgPasswordLockoutPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, addedPolicy)
if err != nil {
@@ -30,6 +33,9 @@ func (c *Commands) AddPasswordLockoutPolicy(ctx context.Context, resourceOwner s
}
func (c *Commands) ChangePasswordLockoutPolicy(ctx context.Context, resourceOwner string, policy *domain.PasswordLockoutPolicy) (*domain.PasswordLockoutPolicy, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-3J9fs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordLockoutPolicyWriteModel(resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
@@ -57,6 +63,9 @@ func (c *Commands) ChangePasswordLockoutPolicy(ctx context.Context, resourceOwne
}
func (c *Commands) RemovePasswordLockoutPolicy(ctx context.Context, orgID string) error {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-4J9fs", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgPasswordLockoutPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {

View File

@@ -0,0 +1,396 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/policy"
)
func TestCommandSide_AddPasswordLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordLockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "mail template already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
{
name: "add policy,ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddPasswordLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangePasswordLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
policy *domain.PasswordLockoutPolicy
}
type res struct {
want *domain.PasswordLockoutPolicy
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 10,
ShowLockOutFailures: true,
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newPasswordLockoutPolicyChangedEvent(context.Background(), "org1", 5, false),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
policy: &domain.PasswordLockoutPolicy{
MaxAttempts: 5,
ShowLockOutFailures: false,
},
},
res: res{
want: &domain.PasswordLockoutPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: "org1",
ResourceOwner: "org1",
},
MaxAttempts: 5,
ShowLockOutFailures: false,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangePasswordLockoutPolicy(tt.args.ctx, tt.args.orgID, tt.args.policy)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemovePasswordLockoutPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "org id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewPasswordLockoutPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
10,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewPasswordLockoutPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemovePasswordLockoutPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func newPasswordLockoutPolicyChangedEvent(ctx context.Context, orgID string, maxAttempts uint64, showLockoutFailure bool) *org.PasswordLockoutPolicyChangedEvent {
event, _ := org.NewPasswordLockoutPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.PasswordLockoutPolicyChanges{
policy.ChangeMaxAttempts(maxAttempts),
policy.ChangeShowLockOutFailures(showLockoutFailure),
},
)
return event
}

View File

@@ -22,7 +22,7 @@ func (s *Step7) execute(ctx context.Context, commandSide *Commands) error {
func (c *Commands) SetupStep7(ctx context.Context, step *Step7) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
secondFactorModel := NewIAMSecondFactorWriteModel()
secondFactorModel := NewIAMSecondFactorWriteModel(domain.SecondFactorTypeOTP)
iamAgg := IAMAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
if !step.OTP {
return []eventstore.EventPusher{}, nil

View File

@@ -22,7 +22,7 @@ func (s *Step8) execute(ctx context.Context, commandSide *Commands) error {
func (c *Commands) SetupStep8(ctx context.Context, step *Step8) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
secondFactorModel := NewIAMSecondFactorWriteModel()
secondFactorModel := NewIAMSecondFactorWriteModel(domain.SecondFactorTypeU2F)
iamAgg := IAMAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
if !step.U2F {
return []eventstore.EventPusher{}, nil

View File

@@ -22,7 +22,7 @@ func (s *Step9) execute(ctx context.Context, commandSide *Commands) error {
func (c *Commands) SetupStep9(ctx context.Context, step *Step9) error {
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
multiFactorModel := NewIAMMultiFactorWriteModel()
multiFactorModel := NewIAMMultiFactorWriteModel(domain.MultiFactorTypeU2FWithPIN)
iamAgg := IAMAggregateFromWriteModel(&multiFactorModel.MultiFactoryWriteModel.WriteModel)
if !step.Passwordless {
return []eventstore.EventPusher{}, nil

View File

@@ -17,7 +17,7 @@ import (
func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName string) (*domain.ObjectDetails, error) {
if orgID == "" || userID == "" || userName == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2N9fs", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2N9fs", "Errors.IDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, orgID)
@@ -35,7 +35,7 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.OrgIAM.NotExisting")
}
if err := CheckOrgIAMPolicyForUserName(userName, orgIAMPolicy); err != nil {
@@ -57,7 +57,7 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
func (c *Commands) DeactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-m0gDf", "Errors.User.UserIDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
@@ -85,7 +85,7 @@ func (c *Commands) DeactivateUser(ctx context.Context, userID, resourceOwner str
func (c *Commands) ReactivateUser(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M9ds", "Errors.User.UserIDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
@@ -113,7 +113,7 @@ func (c *Commands) ReactivateUser(ctx context.Context, userID, resourceOwner str
func (c *Commands) LockUser(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0sd", "Errors.User.UserIDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
@@ -141,7 +141,7 @@ func (c *Commands) LockUser(ctx context.Context, userID, resourceOwner string) (
func (c *Commands) UnlockUser(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-M0dse", "Errors.User.UserIDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
@@ -169,7 +169,7 @@ func (c *Commands) UnlockUser(ctx context.Context, userID, resourceOwner string)
func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, resourceOwner)
@@ -182,7 +182,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, existingUser.ResourceOwner)
if err != nil {
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.OrgIAM.NotExisting")
}
var events []eventstore.EventPusher
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
@@ -210,7 +210,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
func (c *Commands) AddUserToken(ctx context.Context, orgID, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*domain.Token, error) {
if orgID == "" || userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-55n8M", "Errors.IDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-55n8M", "Errors.IDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, orgID)
@@ -286,6 +286,9 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
}
func (c *Commands) UserDomainClaimedSent(ctx context.Context, orgID, userID string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-5m0fs", "Errors.IDMissing")
}
existingUser, err := c.userWriteModelByID(ctx, userID, orgID)
if err != nil {
return err

View File

@@ -6,7 +6,7 @@ import (
)
func writeModelToHuman(wm *HumanWriteModel) *domain.Human {
return &domain.Human{
human := &domain.Human{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Username: wm.UserName,
State: wm.UserState,
@@ -22,14 +22,22 @@ func writeModelToHuman(wm *HumanWriteModel) *domain.Human {
EmailAddress: wm.Email,
IsEmailVerified: wm.IsEmailVerified,
},
Address: &domain.Address{
}
if wm.Phone != "" {
human.Phone = &domain.Phone{
PhoneNumber: wm.Phone,
}
}
if wm.Country != "" || wm.Locality != "" || wm.PostalCode != "" || wm.Region != "" || wm.StreetAddress != "" {
human.Address = &domain.Address{
Country: wm.Country,
Locality: wm.Locality,
PostalCode: wm.PostalCode,
Region: wm.Region,
StreetAddress: wm.StreetAddress,
},
}
}
return human
}
func writeModelToProfile(wm *HumanProfileWriteModel) *domain.Profile {
@@ -73,8 +81,10 @@ func writeModelToAddress(wm *HumanAddressWriteModel) *domain.Address {
func writeModelToMachine(wm *MachineWriteModel) *domain.Machine {
return &domain.Machine{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
Username: wm.UserName,
Name: wm.Name,
Description: wm.Description,
State: wm.UserState,
}
}

View File

@@ -40,7 +40,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if !human.IsValid() {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
}
return c.createHuman(ctx, orgID, human, nil, false)
@@ -82,8 +82,8 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
}
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-9dk45", "Errors.User.Invalid")
if orgID == "" || !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
return c.createHuman(ctx, orgID, human, externalIDP, true)
}
@@ -96,17 +96,17 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
human.AggregateID = userID
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, nil, err
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, nil, err
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.OrgIAMPolicy.NotFound")
}
if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil {
return nil, nil, err
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexity.NotFound")
}
human.SetNamesAsDisplayname()
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, true); err != nil {
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, !selfregister); err != nil {
return nil, nil, err
}
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
@@ -155,7 +155,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
func (c *Commands) HumanSkipMFAInit(ctx context.Context, userID, resourceowner string) (err error) {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2xpX9", "Errors.User.UserIDMissing")
}
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, resourceowner)
@@ -236,10 +236,13 @@ func createRegisterHumanEvent(ctx context.Context, aggregate *eventstore.Aggrega
func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []string) error {
if agentID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
}
events := make([]eventstore.EventPusher, len(userIDs))
for i, userID := range userIDs {
if len(userIDs) == 0 {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-M0od3", "Errors.User.UserIDMissing")
}
events := make([]eventstore.EventPusher, 0)
for _, userID := range userIDs {
existingUser, err := c.getHumanWriteModelByID(ctx, userID, "")
if err != nil {
return err
@@ -247,12 +250,14 @@ func (c *Commands) HumansSignOut(ctx context.Context, agentID string, userIDs []
if !isUserStateExists(existingUser.UserState) {
continue
}
events[i] = user.NewHumanSignedOutEvent(
events = append(events, user.NewHumanSignedOutEvent(
ctx,
UserAggregateFromWriteModel(&existingUser.WriteModel),
agentID)
agentID))
}
if len(events) == 0 {
return nil
}
_, err := c.eventstore.PushEvents(ctx, events...)
return err
}

View File

@@ -13,10 +13,13 @@ func (c *Commands) ChangeHumanAddress(ctx context.Context, address *domain.Addre
return nil, err
}
if existingAddress.State == domain.AddressStateUnspecified || existingAddress.State == domain.AddressStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-0pLdo", "Errors.User.Address.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-0pLdo", "Errors.User.Address.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingAddress.WriteModel)
changedEvent, hasChanged := existingAddress.NewChangedEvent(ctx, userAgg, address.Country, address.Locality, address.PostalCode, address.Region, address.StreetAddress)
changedEvent, hasChanged, err := existingAddress.NewChangedEvent(ctx, userAgg, address.Country, address.Locality, address.PostalCode, address.Region, address.StreetAddress)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0cs", "Errors.User.Address.NotChanged")
}

View File

@@ -2,9 +2,9 @@ package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/user"
)
@@ -87,28 +87,31 @@ func (wm *HumanAddressWriteModel) NewChangedEvent(
postalCode,
region,
streetAddress string,
) (*user.HumanAddressChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewHumanAddressChangedEvent(ctx, aggregate)
) (*user.HumanAddressChangedEvent, bool, error) {
changes := make([]user.AddressChanges, 0)
var err error
if wm.Country != country {
hasChanged = true
changedEvent.Country = &country
changes = append(changes, user.ChangeCountry(country))
}
if wm.Locality != locality {
hasChanged = true
changedEvent.Locality = &locality
changes = append(changes, user.ChangeLocality(locality))
}
if wm.PostalCode != postalCode {
hasChanged = true
changedEvent.PostalCode = &postalCode
changes = append(changes, user.ChangePostalCode(postalCode))
}
if wm.Region != region {
hasChanged = true
changedEvent.Region = &region
changes = append(changes, user.ChangeRegion(region))
}
if wm.StreetAddress != streetAddress {
hasChanged = true
changedEvent.StreetAddress = &streetAddress
changes = append(changes, user.ChangeStreetAddress(streetAddress))
}
return changedEvent, hasChanged
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := user.NewAddressChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@@ -0,0 +1,209 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_ChangeHumanAddress(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
address *domain.Address
resourceOwner string
}
type res struct {
want *domain.Address
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
address: &domain.Address{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Country: "Switzerland",
Locality: "St. Gallen",
PostalCode: "9000",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "address not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email",
true,
),
),
eventFromEventPusher(
newAddressChangedEvent(context.Background(),
"user1", "org1",
"country",
"locality",
"postalcode",
"region",
"street",
),
),
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Address{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Country: "country",
Locality: "locality",
PostalCode: "postalcode",
Region: "region",
StreetAddress: "street",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "address changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddressChangedEvent(context.Background(),
"user1", "org1",
"country",
"locality",
"postalcode",
"region",
"street",
),
),
},
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Address{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Country: "country",
Locality: "locality",
PostalCode: "postalcode",
Region: "region",
StreetAddress: "street",
},
resourceOwner: "org1",
},
res: res{
want: &domain.Address{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Country: "country",
Locality: "locality",
PostalCode: "postalcode",
Region: "region",
StreetAddress: "street",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeHumanAddress(tt.args.ctx, tt.args.address)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newAddressChangedEvent(ctx context.Context, userID, resourceOwner, country, locality, postalCode, region, street string) *user.HumanAddressChangedEvent {
event, _ := user.NewAddressChangedEvent(ctx,
&user.NewAggregate(userID, resourceOwner).Aggregate,
[]user.AddressChanges{
user.ChangeCountry(country),
user.ChangeLocality(locality),
user.ChangePostalCode(postalCode),
user.ChangeRegion(region),
user.ChangeStreetAddress(street),
},
)
return event
}

View File

@@ -13,7 +13,7 @@ import (
func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*domain.Email, error) {
if !email.IsValid() || email.AggregateID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M9sf", "Errors.Email.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M9sf", "Errors.Email.Invalid")
}
existingEmail, err := c.emailWriteModel(ctx, email.AggregateID, email.ResourceOwner)
@@ -21,7 +21,7 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*
return nil, err
}
if existingEmail.UserState == domain.UserStateUnspecified || existingEmail.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-0Pe4r", "Errors.User.Email.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-0Pe4r", "Errors.User.Email.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingEmail.WriteModel)
changedEvent, hasChanged := existingEmail.NewChangedEvent(ctx, userAgg, email.EmailAddress)
@@ -54,10 +54,10 @@ func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email) (*
func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceowner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
}
if code == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-çm0ds", "Errors.User.Code.Empty")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-çm0ds", "Errors.User.Code.Empty")
}
existingCode, err := c.emailWriteModel(ctx, userID, resourceowner)
@@ -89,7 +89,7 @@ func (c *Commands) VerifyHumanEmail(ctx context.Context, userID, code, resourceo
func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
}
existingEmail, err := c.emailWriteModel(ctx, userID, resourceOwner)
@@ -122,6 +122,9 @@ func (c *Commands) CreateHumanEmailVerificationCode(ctx context.Context, userID,
}
func (c *Commands) HumanEmailVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-4m9fs", "Errors.IDMissing")
}
existingEmail, err := c.emailWriteModel(ctx, userID, orgID)
if err != nil {
return err

View File

@@ -82,11 +82,8 @@ func (wm *HumanEmailWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
email string,
) (*user.HumanEmailChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewHumanEmailChangedEvent(ctx, aggregate)
if wm.Email != email {
hasChanged = true
changedEvent.EmailAddress = email
if wm.Email == email {
return nil, false
}
return changedEvent, hasChanged
return user.NewHumanEmailChangedEvent(ctx, aggregate, email), true
}

View File

@@ -0,0 +1,822 @@
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_ChangeHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
email *domain.Email
resourceOwner string
}
type res struct {
want *domain.Email
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid email, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
email: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
email: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
EmailAddress: "email@test.com",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "email not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
email: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
EmailAddress: "email@test.ch",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "verified email changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email-changed@test.ch",
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
email: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
EmailAddress: "email-changed@test.ch",
IsEmailVerified: true,
},
resourceOwner: "org1",
},
res: res{
want: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
EmailAddress: "email-changed@test.ch",
IsEmailVerified: true,
},
},
},
{
name: "email changed with code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email-changed@test.ch",
),
),
eventFromEventPusher(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
email: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
EmailAddress: "email-changed@test.ch",
},
resourceOwner: "org1",
},
res: res{
want: &domain.Email{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
EmailAddress: "email-changed@test.ch",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
}
got, err := r.ChangeHumanEmail(tt.args.ctx, tt.args.email)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_VerifyHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
userID string
code string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "code not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailVerificationFailedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "test",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
}
got, err := r.VerifyHumanEmail(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_CreateVerificationCodeHumanEmail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "user not initialized, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "email already verified, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email2@test.ch",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
emailVerificationCode: tt.fields.secretGenerator,
}
got, err := r.CreateHumanEmailVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_EmailVerificationCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email2@test.ch",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.HumanEmailVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -11,8 +11,11 @@ import (
)
func (c *Commands) BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing")
}
if len(externalIDPs) == 0 {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
}
events := make([]eventstore.EventPusher, len(externalIDPs))
@@ -31,15 +34,18 @@ func (c *Commands) BulkAddedHumanExternalIDP(ctx context.Context, userID, resour
}
func (c *Commands) addHumanExternalIDP(ctx context.Context, humanAgg *eventstore.Aggregate, externalIDP *domain.ExternalIDP) (eventstore.EventPusher, error) {
if externalIDP.AggregateID != "" && humanAgg.ID != externalIDP.AggregateID {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-33M0g", "Errors.IDMissing")
}
if !externalIDP.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid")
}
_, err := c.getOrgIDPConfigByID(ctx, externalIDP.IDPConfigID, humanAgg.ResourceOwner)
if caos_errs.IsNotFound(err) {
_, err = c.getIAMIDPConfigByID(ctx, externalIDP.IDPConfigID)
}
if err != nil {
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting")
}
return user.NewHumanExternalIDPAddedEvent(ctx, humanAgg, externalIDP.IDPConfigID, externalIDP.DisplayName, externalIDP.ExternalUserID), nil
}
@@ -61,8 +67,8 @@ func (c *Commands) RemoveHumanExternalIDP(ctx context.Context, externalIDP *doma
}
func (c *Commands) removeHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP, cascade bool) (eventstore.EventPusher, *HumanExternalIDPWriteModel, error) {
if externalIDP.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9ds", "Errors.IDMissing")
if !externalIDP.IsValid() || externalIDP.AggregateID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M9ds", "Errors.IDMissing")
}
existingExternalIDP, err := c.externalIDPWriteModelByID(ctx, externalIDP.AggregateID, externalIDP.IDPConfigID, externalIDP.ExternalUserID, externalIDP.ResourceOwner)
@@ -81,7 +87,7 @@ func (c *Commands) removeHumanExternalIDP(ctx context.Context, externalIDP *doma
func (c *Commands) HumanExternalLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) {
if userID == "" {
return caos_errs.ThrowNotFound(nil, "COMMAND-5n8sM", "Errors.IDMissing")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-5n8sM", "Errors.IDMissing")
}
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, orgID)
@@ -89,7 +95,7 @@ func (c *Commands) HumanExternalLoginChecked(ctx context.Context, orgID, userID
return err
}
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-dn88J", "Errors.User.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dn88J", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)

View File

@@ -0,0 +1,582 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/iam"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
externalIDPs []*domain.ExternalIDP
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "missing userid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "",
externalIDPs: []*domain.ExternalIDP{
{
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "no external idps, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "userID doesnt match aggregate id, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user2",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid external idp, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "",
ExternalUserID: "externaluser1",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "config not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "add external idp org config, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddExternalIDPUniqueConstraint("config1", "externaluser1")),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
DisplayName: "name",
ExternalUserID: "externaluser1",
},
},
},
res: res{},
},
{
name: "add external idp iam config, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(
eventFromEventPusher(
iam.NewIDPConfigAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
"config1",
"name",
domain.IDPConfigTypeOIDC,
domain.IDPConfigStylingTypeUnspecified,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddExternalIDPUniqueConstraint("config1", "externaluser1")),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
DisplayName: "name",
ExternalUserID: "externaluser1",
},
},
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.BulkAddedHumanExternalIDP(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.externalIDPs)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCommandSide_RemoveExternalIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
externalIDP *domain.ExternalIDP
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid idp, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "",
ExternalUserID: "externaluser1",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "aggregate id missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
eventFromEventPusher(
user.NewUserRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "external idp not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove external idp, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"externaluser1",
),
),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("config1", "externaluser1")),
),
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveHumanExternalIDP(tt.args.ctx, tt.args.externalIDP)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ExternalLoginCheck(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
userID string
authRequest *domain.AuthRequest
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user removed, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
"externaluser1",
),
),
eventFromEventPusher(
user.NewUserRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "external login check, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&user.AuthRequestInfo{
ID: "request1",
UserAgentID: "useragent1",
SelectedIDPConfigID: "config1",
},
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
authRequest: &domain.AuthRequest{
ID: "request1",
AgentID: "useragent1",
SelectedIDPConfigID: "config1",
},
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.HumanExternalLoginChecked(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.authRequest)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -13,7 +13,7 @@ import (
//ResendInitialMail resend inital mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
}
existingCode, err := c.getHumanInitWriteModelByID(ctx, userID, resourceOwner)
@@ -50,10 +50,10 @@ func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourc
func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwner, code, passwordString string) error {
if userID == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-mkM9f", "Errors.User.UserIDMissing")
}
if code == "" {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-44G8s", "Errors.User.Code.Empty")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-44G8s", "Errors.User.Code.Empty")
}
existingCode, err := c.getHumanInitWriteModelByID(ctx, userID, resourceOwner)
@@ -79,6 +79,7 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
}
if passwordString != "" {
passwordWriteModel := NewHumanPasswordWriteModel(userID, existingCode.ResourceOwner)
passwordWriteModel.UserState = domain.UserStateActive
password := &domain.Password{
SecretString: passwordString,
ChangeRequired: false,
@@ -89,12 +90,14 @@ func (c *Commands) HumanVerifyInitCode(ctx context.Context, userID, resourceOwne
}
events = append(events, passwordEvent)
}
events = append(events, user.NewHumanInitialCodeSentEvent(ctx, userAgg))
_, err = c.eventstore.PushEvents(ctx, events...)
return err
}
func (c *Commands) HumanInitCodeSent(ctx context.Context, orgID, userID string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M9fs", "Errors.IDMissing")
}
existingInitCode, err := c.getHumanInitWriteModelByID(ctx, userID, orgID)
if err != nil {
return err

View File

@@ -84,11 +84,6 @@ func (wm *HumanInitCodeWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
email string,
) (*user.HumanEmailChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewHumanEmailChangedEvent(ctx, aggregate)
if wm.Email != email {
hasChanged = true
changedEvent.EmailAddress = email
}
return changedEvent, hasChanged
changedEvent := user.NewHumanEmailChangedEvent(ctx, aggregate, email)
return changedEvent, wm.Email != email
}

View File

@@ -0,0 +1,720 @@
package command
import (
"context"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_ResendInitialMail(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
userID string
email string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "user not initialized, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate)),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "new code email not changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
email: "email@test.ch",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "new code with change email, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanEmailChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"email2@test.ch")),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
email: "email2@test.ch",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
initializeUserCode: tt.fields.secretGenerator,
}
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.email, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_VerifyInitCode(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
userPasswordAlg crypto.HashAlgorithm
}
type args struct {
ctx context.Context
userID string
code string
resourceOwner string
password string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "code not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitializedCheckFailedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "test",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "valid code with password, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate)),
eventFromEventPusherWithCreationDateNow(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitializedCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
eventFromEventPusher(
user.NewHumanPasswordChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeHash,
Algorithm: "hash",
KeyID: "",
Crypted: []byte("password"),
},
false,
"")),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
password: "password",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
initializeUserCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
}
err := r.HumanVerifyInitCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.code, tt.args.password)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCommandSide_InitCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanInitialCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.HumanInitCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -12,22 +12,22 @@ import (
func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string) (*domain.OTP, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
}
human, err := c.getHuman(ctx, userID, resourceowner)
if err != nil {
logging.Log("COMMAND-DAqe1").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get human for loginname")
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-MM9fs", "Errors.User.NotFound")
}
org, err := c.getOrg(ctx, human.ResourceOwner)
if err != nil {
logging.Log("COMMAND-Cm0ds").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org for loginname")
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-55M9f", "Errors.Org.NotFound")
}
orgPolicy, err := c.getOrgIAMPolicy(ctx, org.AggregateID)
if err != nil {
logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
return nil, err
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.OrgIAM.NotFound")
}
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil {
@@ -114,7 +114,7 @@ func (c *Commands) HumanCheckMFAOTP(ctx context.Context, userID, code, resourceo
func (c *Commands) HumanRemoveOTP(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
}
existingOTP, err := c.otpWriteModelByID(ctx, userID, resourceOwner)

View File

@@ -0,0 +1,339 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_AddHumanOTP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
}
)
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "org not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "org iam policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&user.NewAggregate("org1", "org1").Aggregate,
"org",
),
),
),
expectFilter(),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "otp already exists, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewOrgAddedEvent(context.Background(),
&user.NewAggregate("org1", "org1").Aggregate,
"org",
),
),
),
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
user.NewHumanOTPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
}),
),
eventFromEventPusher(
user.NewHumanOTPVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"agent1")),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsErrorAlreadyExists,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddHumanOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RemoveHumanOTP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type (
args struct {
ctx context.Context
orgID string
userID string
}
)
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "otp not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "otp not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanOTPAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
nil,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanOTPRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
userID: "user1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.HumanRemoveOTP(tt.args.ctx, tt.args.userID, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}

View File

@@ -14,11 +14,16 @@ import (
func (c *Commands) SetOneTimePassword(ctx context.Context, orgID, userID, passwordString string) (objectDetails *domain.ObjectDetails, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.IDMissing")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return nil, err
}
if !existingPassword.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0fs", "Errors.User.NotFound")
}
password := &domain.Password{
SecretString: passwordString,
ChangeRequired: true,
@@ -43,16 +48,22 @@ func (c *Commands) SetPassword(ctx context.Context, orgID, userID, code, passwor
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M9fs", "Errors.IDMissing")
}
if passwordString == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Mf0sd", "Errors.User.Password.Empty")
}
existingCode, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return err
}
if existingCode.Code == nil || existingCode.UserState == domain.UserStateUnspecified || existingCode.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.Code.NotFound")
}
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.emailVerificationCode)
err = crypto.VerifyCode(existingCode.CodeCreationDate, existingCode.CodeExpiry, existingCode.Code, code, c.passwordVerificationCode)
if err != nil {
return err
}
@@ -74,6 +85,12 @@ func (c *Commands) ChangePassword(ctx context.Context, orgID, userID, oldPasswor
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.IDMissing")
}
if oldPassword == "" || newPassword == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M0fs", "Errors.User.Password.Empty")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return nil, err
@@ -114,7 +131,7 @@ func (c *Commands) changePassword(ctx context.Context, userAgentID string, passw
defer func() { span.EndWithError(err) }()
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Email.NotFound")
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-G8dh3", "Errors.User.Password.NotFound")
}
if existingPassword.UserState == domain.UserStateInitial {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-M9dse", "Errors.User.NotInitialised")
@@ -130,12 +147,16 @@ func (c *Commands) changePassword(ctx context.Context, userAgentID string, passw
}
func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner string, notifyType domain.NotificationType) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-M00oL", "Errors.User.UserIDMissing")
}
existingHuman, err := c.userWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Hj9ds", "Errors.User.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Hj9ds", "Errors.User.NotFound")
}
if existingHuman.UserState == domain.UserStateInitial {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sd", "Errors.User.NotInitialised")
@@ -157,12 +178,16 @@ func (c *Commands) RequestSetPassword(ctx context.Context, userID, resourceOwner
}
func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-MM9fs", "Errors.User.UserIDMissing")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
if err != nil {
return err
}
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
_, err = c.eventstore.PushEvents(ctx, user.NewHumanPasswordCodeSentEvent(ctx, userAgg))
@@ -173,8 +198,11 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-4Mfsf", "Errors.User.UserIDMissing")
}
if password == "" {
return caos_errs.ThrowNotFound(nil, "COMMAND-3n8fs", "Errors.User.Password.Empty")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-3n8fs", "Errors.User.Password.Empty")
}
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
@@ -182,11 +210,11 @@ func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, passwo
return err
}
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.NotFound")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.NotFound")
}
if existingPassword.Secret == nil {
return caos_errs.ThrowNotFound(nil, "COMMAND-3n77z", "Errors.User.Password.NotSet")
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3n77z", "Errors.User.Password.NotSet")
}
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,15 @@ import (
func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone) (*domain.Phone, error) {
if !phone.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid")
}
existingPhone, err := c.phoneWriteModelByID(ctx, phone.AggregateID, phone.ResourceOwner)
if err != nil {
return nil, err
}
if !existingPhone.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-aM9cs", "Errors.User.Phone.NotFound")
if !existingPhone.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M0fs", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
@@ -56,17 +56,20 @@ func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone) (*
func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceowner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Km9ds", "Errors.User.UserIDMissing")
}
if code == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-wMe9f", "Errors.User.Code.Empty")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-wMe9f", "Errors.User.Code.Empty")
}
existingCode, err := c.phoneWriteModelByID(ctx, userID, resourceowner)
if err != nil {
return nil, err
}
if !existingCode.State.Exists() {
if !existingCode.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Rsj8c", "Errors.User.NotFound")
}
if !existingCode.State.Exists() || existingCode.Code == nil {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-Rsj8c", "Errors.User.Code.NotFound")
}
@@ -90,7 +93,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
}
existingPhone, err := c.phoneWriteModelByID(ctx, userID, resourceowner)
@@ -98,6 +101,9 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
return nil, err
}
if !existingPhone.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.NotFound")
}
if !existingPhone.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-2b7Hf", "Errors.User.Phone.NotFound")
}
@@ -123,10 +129,17 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
}
func (c *Commands) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, userID string) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-3m9Fs", "Errors.User.UserIDMissing")
}
existingPhone, err := c.phoneWriteModelByID(ctx, userID, orgID)
if err != nil {
return err
}
if !existingPhone.UserState.Exists() {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9fs", "Errors.User.NotFound")
}
if !existingPhone.State.Exists() {
return caos_errs.ThrowNotFound(nil, "COMMAND-66n8J", "Errors.User.Phone.NotFound")
}
@@ -138,13 +151,16 @@ func (c *Commands) HumanPhoneVerificationCodeSent(ctx context.Context, orgID, us
func (c *Commands) RemoveHumanPhone(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
if userID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.User.UserIDMissing")
}
existingPhone, err := c.phoneWriteModelByID(ctx, userID, resourceOwner)
if err != nil {
return nil, err
}
if !existingPhone.UserState.Exists() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9fs", "Errors.User.NotFound")
}
if !existingPhone.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-p6rsc", "Errors.User.Phone.NotFound")
}

View File

@@ -20,7 +20,8 @@ type HumanPhoneWriteModel struct {
CodeCreationDate time.Time
CodeExpiry time.Duration
State domain.PhoneState
State domain.PhoneState
UserState domain.UserState
}
func NewHumanPhoneWriteModel(userID, resourceOwner string) *HumanPhoneWriteModel {
@@ -38,13 +39,15 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
case *user.HumanAddedEvent:
if e.PhoneNumber != "" {
wm.Phone = e.PhoneNumber
wm.State = domain.PhoneStateActive
}
wm.State = domain.PhoneStateActive
wm.UserState = domain.UserStateActive
case *user.HumanRegisteredEvent:
if e.PhoneNumber != "" {
wm.Phone = e.PhoneNumber
wm.State = domain.PhoneStateActive
}
wm.UserState = domain.UserStateActive
case *user.HumanPhoneChangedEvent:
wm.Phone = e.PhoneNumber
wm.IsPhoneVerified = false
@@ -60,7 +63,7 @@ func (wm *HumanPhoneWriteModel) Reduce() error {
case *user.HumanPhoneRemovedEvent:
wm.State = domain.PhoneStateRemoved
case *user.UserRemovedEvent:
wm.State = domain.PhoneStateRemoved
wm.UserState = domain.UserStateDeleted
}
}
return wm.WriteModel.Reduce()
@@ -84,11 +87,6 @@ func (wm *HumanPhoneWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
phone string,
) (*user.HumanPhoneChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewHumanPhoneChangedEvent(ctx, aggregate)
if wm.Phone != phone {
hasChanged = true
changedEvent.PhoneNumber = phone
}
return changedEvent, hasChanged
changedEvent := user.NewHumanPhoneChangedEvent(ctx, aggregate, phone)
return changedEvent, phone != wm.Phone
}

View File

@@ -0,0 +1,962 @@
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_ChangeHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
email *domain.Phone
resourceOwner string
}
type res struct {
want *domain.Phone
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid phone, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "phone not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+41711234567",
),
),
),
),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "verified phone changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+41711234567",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+41719876543",
),
),
eventFromEventPusher(
user.NewHumanPhoneVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0719876543",
IsPhoneVerified: true,
},
resourceOwner: "org1",
},
res: res{
want: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
PhoneNumber: "+41719876543",
IsPhoneVerified: true,
},
},
},
{
name: "phone changed with code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+41711234567",
),
),
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
email: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
PhoneNumber: "0711234567",
},
resourceOwner: "org1",
},
res: res{
want: &domain.Phone{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
PhoneNumber: "+41711234567",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
}
got, err := r.ChangeHumanPhone(tt.args.ctx, tt.args.email)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_VerifyHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
userID string
code string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "code missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "code not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "aa",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "invalid code, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneVerificationFailedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "test",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "valid code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
code: "a",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
}
got, err := r.VerifyHumanPhone(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretGenerator crypto.Generator
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "phone already verified, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
eventFromEventPusher(
user.NewHumanPhoneVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "new code, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
),
),
secretGenerator: GetMockSecretGenerator(t),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
phoneVerificationCode: tt.fields.secretGenerator,
}
got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_PhoneVerificationCodeSent(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "code sent, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusher(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneCodeSentEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.HumanPhoneVerificationCodeSent(tt.args.ctx, tt.args.resourceOwner, tt.args.userID)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestCommandSide_RemoveHumanPhone(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
userID string
resourceOwner string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "userid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "phone not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "remove phone, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email@test.ch",
true,
),
),
eventFromEventPusherWithCreationDateNow(
user.NewHumanPhoneChangedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"+411234567",
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanPhoneRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveHumanPhone(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}

View File

@@ -18,9 +18,13 @@ func (c *Commands) ChangeHumanProfile(ctx context.Context, profile *domain.Profi
return nil, err
}
if existingProfile.UserState == domain.UserStateUnspecified || existingProfile.UserState == domain.UserStateDeleted {
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-3M9sd", "Errors.User.Profile.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingProfile.WriteModel)
changedEvent, hasChanged, err := existingProfile.NewChangedEvent(ctx, userAgg, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender)
if err != nil {
return nil, err
}
changedEvent, hasChanged := existingProfile.NewChangedEvent(ctx, profile.FirstName, profile.LastName, profile.NickName, profile.DisplayName, profile.PreferredLanguage, profile.Gender)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M0fs", "Errors.User.Profile.NotChanged")
}

View File

@@ -89,39 +89,41 @@ func (wm *HumanProfileWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *HumanProfileWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
firstName,
lastName,
nickName,
displayName string,
preferredLanguage language.Tag,
gender domain.Gender,
) (*user.HumanProfileChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewHumanProfileChangedEvent(ctx, UserAggregateFromWriteModel(&wm.WriteModel))
) (*user.HumanProfileChangedEvent, bool, error) {
changes := make([]user.ProfileChanges, 0)
var err error
if wm.FirstName != firstName {
hasChanged = true
changedEvent.FirstName = firstName
changes = append(changes, user.ChangeFirstName(firstName))
}
if wm.LastName != lastName {
hasChanged = true
changedEvent.LastName = lastName
changes = append(changes, user.ChangeLastName(lastName))
}
if wm.NickName != nickName {
hasChanged = true
changedEvent.NickName = &nickName
changes = append(changes, user.ChangeNickName(nickName))
}
if wm.DisplayName != displayName {
hasChanged = true
changedEvent.DisplayName = &displayName
changes = append(changes, user.ChangeDisplayName(displayName))
}
if wm.PreferredLanguage != preferredLanguage {
hasChanged = true
changedEvent.PreferredLanguage = &preferredLanguage
changes = append(changes, user.ChangePreferredLanguage(preferredLanguage))
}
if gender.Valid() && wm.Gender != gender {
hasChanged = true
changedEvent.Gender = &gender
if wm.Gender != gender {
changes = append(changes, user.ChangeGender(gender))
}
return changedEvent, hasChanged
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := user.NewHumanProfileChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@@ -0,0 +1,207 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_ChangeHumanProfile(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
address *domain.Profile
resourceOwner string
}
type res struct {
want *domain.Profile
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
address: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
FirstName: "firstname",
LastName: "lastname",
NickName: "nickname",
DisplayName: "displayname",
PreferredLanguage: language.German,
Gender: domain.GenderFemale,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "profile not changed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderFemale,
"email",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
FirstName: "firstname",
LastName: "lastname",
NickName: "nickname",
DisplayName: "displayname",
PreferredLanguage: language.German,
Gender: domain.GenderFemale,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "profile changed, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
language.German,
domain.GenderUnspecified,
"email",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newProfileChangedEvent(context.Background(),
"user1", "org1",
"firstname2",
"lastname2",
"nickname2",
"displayname2",
language.English,
domain.GenderMale,
),
),
},
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: language.English,
Gender: domain.GenderMale,
},
resourceOwner: "org1",
},
res: res{
want: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: language.English,
Gender: domain.GenderMale,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeHumanProfile(tt.args.ctx, tt.args.address)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newProfileChangedEvent(ctx context.Context, userID, resourceOwner, fistName, lastName, nickName, displayName string, lang language.Tag, gender domain.Gender) *user.HumanProfileChangedEvent {
event, _ := user.NewHumanProfileChangedEvent(ctx,
&user.NewAggregate(userID, resourceOwner).Aggregate,
[]user.ProfileChanges{
user.ChangeFirstName(fistName),
user.ChangeLastName(lastName),
user.ChangeNickName(nickName),
user.ChangeDisplayName(displayName),
user.ChangePreferredLanguage(lang),
user.ChangeGender(gender),
},
)
return event
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,21 +13,18 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain
if !machine.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
}
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.OrgIAMPolicy.NotFound")
}
if !orgIAMPolicy.UserLoginMustBeDomain {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.Invalid")
}
userID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
machine.AggregateID = userID
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, err
}
if !orgIAMPolicy.UserLoginMustBeDomain {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.User.Invalid")
}
addedMachine := NewMachineWriteModel(machine.AggregateID, orgID)
userAgg := UserAggregateFromWriteModel(&addedMachine.WriteModel)
events, err := c.eventstore.PushEvents(ctx, user.NewMachineAddedEvent(
@@ -58,7 +55,10 @@ func (c *Commands) ChangeMachine(ctx context.Context, machine *domain.Machine) (
}
userAgg := UserAggregateFromWriteModel(&existingMachine.WriteModel)
changedEvent, hasChanged := existingMachine.NewChangedEvent(ctx, userAgg, machine.Name, machine.Description)
changedEvent, hasChanged, err := existingMachine.NewChangedEvent(ctx, userAgg, machine.Name, machine.Description)
if err != nil {
return nil, err
}
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2n8vs", "Errors.User.NotChanged")
}

View File

@@ -86,16 +86,22 @@ func (wm *MachineWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
name,
description string,
) (*user.MachineChangedEvent, bool) {
hasChanged := false
changedEvent := user.NewMachineChangedEvent(ctx, aggregate)
) (*user.MachineChangedEvent, bool, error) {
changes := make([]user.MachineChanges, 0)
var err error
if wm.Name != name {
hasChanged = true
changedEvent.Name = &name
changes = append(changes, user.ChangeName(name))
}
if wm.Description != description {
hasChanged = true
changedEvent.Description = &description
changes = append(changes, user.ChangeDescription(description))
}
return changedEvent, hasChanged
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := user.NewMachineChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

View File

@@ -0,0 +1,350 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/id"
id_mock "github.com/caos/zitadel/internal/id/mock"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_AddMachine(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
}
type args struct {
ctx context.Context
orgID string
machine *domain.Machine
}
type res struct {
want *domain.Machine
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
Username: "username",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "org policy not found, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
Username: "username",
Name: "name",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "org policy global, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
false,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
Username: "username",
Name: "name",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "add machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"name",
"description",
true,
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
Username: "username",
Description: "description",
Name: "name",
},
},
res: res{
want: &domain.Machine{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Name: "name",
Description: "description",
State: domain.UserStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
}
got, err := r.AddMachine(tt.args.ctx, tt.args.orgID, tt.args.machine)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_ChangeMachine(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
orgID string
machine *domain.Machine
}
type res struct {
want *domain.Machine
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "user invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
Username: "username",
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Username: "username",
Name: "name",
},
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "no changes, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"name",
"description",
true,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Name: "name",
Description: "description",
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "change machine, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewMachineAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"name",
"description",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newMachineChangedEvent(context.Background(), "user1", "org1", "name1", "description1"),
),
},
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
machine: &domain.Machine{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
Description: "description1",
Name: "name1",
},
},
res: res{
want: &domain.Machine{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Name: "name1",
Description: "description1",
State: domain.UserStateActive,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ChangeMachine(tt.args.ctx, tt.args.machine)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func newMachineChangedEvent(ctx context.Context, userID, resourceOwner, name, description string) *user.MachineChangedEvent {
event, _ := user.NewMachineChangedEvent(ctx,
&user.NewAggregate(userID, resourceOwner).Aggregate,
[]user.MachineChanges{
user.ChangeName(name),
user.ChangeDescription(description),
},
)
return event
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,8 @@ const (
SecondFactorTypeUnspecified SecondFactorType = iota
SecondFactorTypeOTP
SecondFactorTypeU2F
secondFactorCount
)
type MultiFactorType int32
@@ -13,6 +15,8 @@ type MultiFactorType int32
const (
MultiFactorTypeUnspecified MultiFactorType = iota
MultiFactorTypeU2FWithPIN
multiFactorCount
)
type FactorState int32
@@ -25,6 +29,14 @@ const (
factorStateCount
)
func (f SecondFactorType) Valid() bool {
return f > 0 && f < secondFactorCount
}
func (f MultiFactorType) Valid() bool {
return f > 0 && f < multiFactorCount
}
func (f FactorState) Valid() bool {
return f >= 0 && f < factorStateCount
}

View File

@@ -18,12 +18,7 @@ type Human struct {
*Email
*Phone
*Address
ExternalIDPs []*ExternalIDP
OTP *OTP
U2FTokens []*WebAuthNToken
PasswordlessTokens []*WebAuthNToken
U2FLogins []*WebAuthNLogin
PasswordlessLogins []*WebAuthNLogin
ExternalIDPs []*ExternalIDP
}
func (h Human) GetUsername() string {
@@ -57,7 +52,7 @@ func (f Gender) Valid() bool {
}
func (u *Human) IsValid() bool {
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
return u.Profile != nil && u.Profile.IsValid() && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
}
func (u *Human) CheckOrgIAMPolicy(policy *OrgIAMPolicy) error {

View File

@@ -3,9 +3,12 @@ package domain
import (
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"regexp"
"time"
)
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
type Email struct {
es_models.ObjectRoot
@@ -21,7 +24,7 @@ type EmailCode struct {
}
func (e *Email) IsValid() bool {
return e.EmailAddress != ""
return e.EmailAddress != "" && emailRegex.MatchString(e.EmailAddress)
}
func NewEmailCode(emailGenerator crypto.Generator) (*EmailCode, error) {

View File

@@ -0,0 +1,74 @@
package domain
import (
"testing"
)
func TestEmailValid(t *testing.T) {
type args struct {
email *Email
}
tests := []struct {
name string
args args
result bool
}{
{
name: "empty email, invalid",
args: args{
email: &Email{},
},
result: false,
},
{
name: "only letters email, invalid",
args: args{
email: &Email{EmailAddress: "testemail"},
},
result: false,
},
{
name: "nothing after @, invalid",
args: args{
email: &Email{EmailAddress: "testemail@"},
},
result: false,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "testemail@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test.email@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test/email@gmail.com"},
},
result: true,
},
{
name: "email, valid",
args: args{
email: &Email{EmailAddress: "test/email@gmail.com"},
},
result: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.args.email.IsValid()
if result != tt.result {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, result)
}
})
}
}

View File

@@ -11,7 +11,7 @@ type ExternalIDP struct {
}
func (idp *ExternalIDP) IsValid() bool {
return idp.AggregateID != "" && idp.IDPConfigID != "" && idp.ExternalUserID != ""
return idp.IDPConfigID != "" && idp.ExternalUserID != ""
}
type ExternalIDPState int32

View File

@@ -34,7 +34,7 @@ func (p *Phone) IsValid() bool {
func (p *Phone) formatPhone() error {
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
if err != nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
return caos_errs.ThrowInvalidArgument(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
}
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
return nil

View File

@@ -0,0 +1,107 @@
package domain
import (
"testing"
caos_errs "github.com/caos/zitadel/internal/errors"
)
func TestFormatPhoneNumber(t *testing.T) {
type args struct {
phone *Phone
}
tests := []struct {
name string
args args
result *Phone
errFunc func(err error) bool
}{
{
name: "invalid phone number",
args: args{
phone: &Phone{
PhoneNumber: "PhoneNumber",
},
},
errFunc: caos_errs.IsErrorInvalidArgument,
},
{
name: "format phone 071...",
args: args{
phone: &Phone{
PhoneNumber: "0711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 0041...",
args: args{
phone: &Phone{
PhoneNumber: "0041711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 071 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "071 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone +4171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "+4171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 004171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format non swiss phone 004371 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004371 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+43711234567",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.phone.formatPhone()
if tt.errFunc == nil && tt.result.PhoneNumber != tt.args.phone.PhoneNumber {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.phone.PhoneNumber, tt.result.PhoneNumber)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@@ -20,5 +20,5 @@ func (m Machine) GetState() UserState {
}
func (sa *Machine) IsValid() bool {
return sa.Name != ""
return sa.Name != "" && sa.Username != ""
}

View File

@@ -17,7 +17,7 @@ type OrgDomain struct {
}
func (domain *OrgDomain) IsValid() bool {
return domain.AggregateID != "" && domain.Domain != ""
return domain.Domain != ""
}
func (domain *OrgDomain) GenerateVerificationCode(codeGenerator crypto.Generator) (string, error) {

View File

@@ -13,3 +13,7 @@ const (
func (f PolicyState) Valid() bool {
return f >= 0 && f < policyStateCount
}
func (s PolicyState) Exists() bool {
return s != PolicyStateUnspecified && s != PolicyStateRemoved
}

View File

@@ -27,6 +27,10 @@ type IDPProvider struct {
IDPState IDPConfigState
}
func (p IDPProvider) IsValid() bool {
return p.IDPConfigID != ""
}
type PasswordlessType int32
const (

View File

@@ -22,3 +22,7 @@ const (
func (f UserState) Valid() bool {
return f >= 0 && f < userStateCount
}
func (s UserState) Exists() bool {
return s != UserStateUnspecified && s != UserStateDeleted
}

View File

@@ -16,7 +16,7 @@ func GetIDPProviderByAggregateIDAndConfigID(db *gorm.DB, table, aggregateID, idp
query := repository.PrepareGetByQuery(table, aggIDQuery, idpConfigIDQuery)
err := query(db, policy)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Skvi8", "Errors.IAM.LoginPolicy.IdpProviderNotExisting")
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Skvi8", "Errors.IAM.LoginPolicy.IDP.NotExisting")
}
return policy, err
}

View File

@@ -15,7 +15,7 @@ func IDPByID(db *gorm.DB, table, idpID string) (*model.IDPConfigView, error) {
query := repository.PrepareGetByQuery(table, idpIDQuery)
err := query(db, idp)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Ahq2s", "Errors.IAM.IdpNotExisting")
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Ahq2s", "Errors.IDP.NotExisting")
}
return idp, err
}

View File

@@ -184,7 +184,7 @@ func (i *ExternalIDP) getOrgIDPConfig(ctx context.Context, aggregateID, idpConfi
if _, i := existing.GetIDP(idpConfigID); i != nil {
return i, nil
}
return nil, caos_errs.ThrowNotFound(nil, "EVENT-22n7G", "Errors.Org.IdpNotExisting")
return nil, caos_errs.ThrowNotFound(nil, "EVENT-22n7G", "Errors.IDP.NotExisting")
}
func (i *ExternalIDP) getOrgByID(ctx context.Context, orgID string) (*org_model.Org, error) {

View File

@@ -23,7 +23,6 @@ func NewIdentityProviderAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
idpConfigID string,
idpProviderType domain.IdentityProviderType,
) *IdentityProviderAddedEvent {
return &IdentityProviderAddedEvent{
@@ -33,7 +32,7 @@ func NewIdentityProviderAddedEvent(
aggregate,
LoginPolicyIDPProviderAddedEventType),
idpConfigID,
idpProviderType),
domain.IdentityProviderTypeSystem),
}
}

View File

@@ -247,14 +247,15 @@ func (e *DomainRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstr
return []*eventstore.EventUniqueConstraint{NewRemoveOrgDomainUniqueConstraint(e.Domain)}
}
func NewDomainRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainRemovedEvent {
func NewDomainRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string, verified bool) *DomainRemovedEvent {
return &DomainRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
OrgDomainRemovedEventType,
),
Domain: domain,
Domain: domain,
isVerified: verified,
}
}

View File

@@ -32,14 +32,57 @@ func (e *HumanAddressChangedEvent) UniqueConstraints() []*eventstore.EventUnique
return nil
}
func NewHumanAddressChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanAddressChangedEvent {
return &HumanAddressChangedEvent{
func NewAddressChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
changes []AddressChanges,
) (*HumanAddressChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "USER-3n8fs", "Errors.NoChangesFound")
}
changeEvent := &HumanAddressChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanAddressChangedType,
),
}
for _, change := range changes {
change(changeEvent)
}
return changeEvent, nil
}
type AddressChanges func(event *HumanAddressChangedEvent)
func ChangeCountry(country string) func(event *HumanAddressChangedEvent) {
return func(e *HumanAddressChangedEvent) {
e.Country = &country
}
}
func ChangeLocality(locality string) func(event *HumanAddressChangedEvent) {
return func(e *HumanAddressChangedEvent) {
e.Locality = &locality
}
}
func ChangePostalCode(code string) func(event *HumanAddressChangedEvent) {
return func(e *HumanAddressChangedEvent) {
e.PostalCode = &code
}
}
func ChangeRegion(region string) func(event *HumanAddressChangedEvent) {
return func(e *HumanAddressChangedEvent) {
e.Region = &region
}
}
func ChangeStreetAddress(street string) func(event *HumanAddressChangedEvent) {
return func(e *HumanAddressChangedEvent) {
e.StreetAddress = &street
}
}
func HumanAddressChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {

View File

@@ -34,13 +34,14 @@ func (e *HumanEmailChangedEvent) UniqueConstraints() []*eventstore.EventUniqueCo
return nil
}
func NewHumanEmailChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanEmailChangedEvent {
func NewHumanEmailChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress string) *HumanEmailChangedEvent {
return &HumanEmailChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanEmailChangedType,
),
EmailAddress: emailAddress,
}
}

View File

@@ -35,13 +35,14 @@ func (e *HumanPhoneChangedEvent) UniqueConstraints() []*eventstore.EventUniqueCo
return nil
}
func NewHumanPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanPhoneChangedEvent {
func NewHumanPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone string) *HumanPhoneChangedEvent {
return &HumanPhoneChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanPhoneChangedType,
),
PhoneNumber: phone,
}
}

View File

@@ -35,14 +35,63 @@ func (e *HumanProfileChangedEvent) UniqueConstraints() []*eventstore.EventUnique
return nil
}
func NewHumanProfileChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanProfileChangedEvent {
return &HumanProfileChangedEvent{
func NewHumanProfileChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
changes []ProfileChanges,
) (*HumanProfileChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "USER-33n8F", "Errors.NoChangesFound")
}
changeEvent := &HumanProfileChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanProfileChangedType,
),
}
for _, change := range changes {
change(changeEvent)
}
return changeEvent, nil
}
type ProfileChanges func(event *HumanProfileChangedEvent)
func ChangeFirstName(firstName string) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.FirstName = firstName
}
}
func ChangeLastName(lastName string) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.LastName = lastName
}
}
func ChangeNickName(nickName string) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.NickName = &nickName
}
}
func ChangeDisplayName(displayName string) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.DisplayName = &displayName
}
}
func ChangePreferredLanguage(language language.Tag) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.PreferredLanguage = &language
}
}
func ChangeGender(gender domain.Gender) func(event *HumanProfileChangedEvent) {
return func(e *HumanProfileChangedEvent) {
e.Gender = &gender
}
}
func HumanProfileChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {

View File

@@ -19,7 +19,7 @@ type MachineAddedEvent struct {
eventstore.BaseEvent `json:"-"`
UserName string `json:"userName"`
UserLoginMustBeDomain bool
userLoginMustBeDomain bool `json:"-"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
@@ -30,7 +30,7 @@ func (e *MachineAddedEvent) Data() interface{} {
}
func (e *MachineAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.UserLoginMustBeDomain)}
return []*eventstore.EventUniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, e.userLoginMustBeDomain)}
}
func NewMachineAddedEvent(
@@ -50,7 +50,7 @@ func NewMachineAddedEvent(
UserName: userName,
Name: name,
Description: description,
UserLoginMustBeDomain: userLoginMustBeDomain,
userLoginMustBeDomain: userLoginMustBeDomain,
}
}
@@ -86,14 +86,36 @@ func (e *MachineChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConst
func NewMachineChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
) *MachineChangedEvent {
return &MachineChangedEvent{
changes []MachineChanges,
) (*MachineChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "USER-3M9fs", "Errors.NoChangesFound")
}
changeEvent := &MachineChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
MachineChangedEventType,
),
}
for _, change := range changes {
change(changeEvent)
}
return changeEvent, nil
}
type MachineChanges func(event *MachineChangedEvent)
func ChangeName(name string) func(event *MachineChangedEvent) {
return func(e *MachineChangedEvent) {
e.Name = &name
}
}
func ChangeDescription(description string) func(event *MachineChangedEvent) {
return func(e *MachineChangedEvent) {
e.Description = &description
}
}
func MachineChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {

View File

@@ -3,6 +3,7 @@ Errors:
NoChangesFound: Keine Änderungen gefunden
OriginNotAllowed: Dieser "Origin" ist nicht freigeschaltet
IDMissing: ID fehlt
ResourceOwnerMissing: Organisation fehlt
User:
NotFound: Benutzer konnte nicht gefunden werden
AlreadyExists: Benutzer existierts bereits
@@ -225,14 +226,11 @@ Errors:
IdpIsNotOIDC: IDP Konfiguration ist nicht vom Typ OIDC
LoginPolicyInvalid: Login Policy ist ungültig
LoginPolicyNotExisting: Login Policy nicht vorhanden
IdpProviderInvalid: Idp Provider ist ungültig
LoginPolicy:
NotFound: Default Login Policy konnte nicht gefunden
NotChanged: Default Login Policy wurde nicht verändert
NotExisting: Default Login Policy existiert nicht
AlreadyExists: Default Login Policy existiert bereits
IdpProviderAlreadyExisting: Idp Provider existiert bereits
IdpProviderNotExisting: Idp Provider existiert nicht
MFA:
AlreadyExists: Multifaktor existiert bereits
NotExisting: Multifaktor existiert nicht
@@ -240,9 +238,9 @@ Errors:
IDP:
AlreadyExists: Identitäts Provider existiert bereits
NotExisting: Identitäts Provider existiert nicht
Invalid: Idp Provider ist ungültig
IDPConfig:
AlreadyExists: Identitäts Provider Konfiguration existiert bereits
NotExisting: Identitäts Provider Konfiguration existiert nicht
NotInactive: Identitäts Provider Konfiguration nicht inaktive
NotActive: Identitäts Provider Konfiguration nicht aktive
LabelPolicy:
@@ -298,6 +296,7 @@ Errors:
AlreadyExists: Member existiert bereits
IDPConfig:
AlreadyExists: IDP Konfiguration mit diesem Name existiert bereits
NotExisting: Identitäts Provider Konfiguration existiert nicht
Changes:
NotFound: Es konnte kein Änderungsverlauf gefunden werden
Token:

Some files were not shown because too many files have changed in this diff Show More