fix(projections): user idp link projection (#2583)

* fix(projections): add app

* fix(migration): add index for project_id

* test: app projection

* fix(projections): add idp_user_link

* test: idp user link

* fix: migration versions

* refactor: rename externalIDP to UserIDPLink

* fix: interface methods
This commit is contained in:
Silvan 2021-11-02 10:08:47 +01:00 committed by GitHub
parent 5ba1e45423
commit 92f9eedbe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 626 additions and 359 deletions

View File

@ -146,10 +146,10 @@ func idpConfigTypeToDomain(idpType iam_model.IDPProviderType) domain.IdentityPro
}
}
func externalIDPViewsToDomain(idps []*user_model.ExternalIDPView) []*domain.ExternalIDP {
externalIDPs := make([]*domain.ExternalIDP, len(idps))
func externalIDPViewsToDomain(idps []*user_model.ExternalIDPView) []*domain.UserIDPLink {
externalIDPs := make([]*domain.UserIDPLink, len(idps))
for i, idp := range idps {
externalIDPs[i] = &domain.ExternalIDP{
externalIDPs[i] = &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: idp.UserID,
ResourceOwner: idp.ResourceOwner,

View File

@ -24,7 +24,7 @@ func (s *Server) ListMyLinkedIDPs(ctx context.Context, req *auth_pb.ListMyLinked
}
func (s *Server) RemoveMyLinkedIDP(ctx context.Context, req *auth_pb.RemoveMyLinkedIDPRequest) (*auth_pb.RemoveMyLinkedIDPResponse, error) {
objectDetails, err := s.command.RemoveHumanExternalIDP(ctx, RemoveMyLinkedIDPRequestToDomain(ctx, req))
objectDetails, err := s.command.RemoveUserIDPLink(ctx, RemoveMyLinkedIDPRequestToDomain(ctx, req))
if err != nil {
return nil, err
}

View File

@ -18,8 +18,8 @@ func ListMyLinkedIDPsRequestToModel(req *auth_pb.ListMyLinkedIDPsRequest) *model
}
}
func RemoveMyLinkedIDPRequestToDomain(ctx context.Context, req *auth_pb.RemoveMyLinkedIDPRequest) *domain.ExternalIDP {
return &domain.ExternalIDP{
func RemoveMyLinkedIDPRequestToDomain(ctx context.Context, req *auth_pb.RemoveMyLinkedIDPRequest) *domain.UserIDPLink {
return &domain.UserIDPLink{
ObjectRoot: ctxToObjectRoot(ctx),
IDPConfigID: req.IdpId,
ExternalUserID: req.LinkedUserId,

View File

@ -148,10 +148,10 @@ func idpConfigTypeToDomain(idpType iam_model.IDPProviderType) domain.IdentityPro
}
}
func externalIDPViewsToDomain(idps []*user_model.ExternalIDPView) []*domain.ExternalIDP {
externalIDPs := make([]*domain.ExternalIDP, len(idps))
func externalIDPViewsToDomain(idps []*user_model.ExternalIDPView) []*domain.UserIDPLink {
externalIDPs := make([]*domain.UserIDPLink, len(idps))
for i, idp := range idps {
externalIDPs[i] = &domain.ExternalIDP{
externalIDPs[i] = &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: idp.UserID,
ResourceOwner: idp.ResourceOwner,

View File

@ -598,7 +598,7 @@ func (s *Server) ListHumanLinkedIDPs(ctx context.Context, req *mgmt_pb.ListHuman
}, nil
}
func (s *Server) RemoveHumanLinkedIDP(ctx context.Context, req *mgmt_pb.RemoveHumanLinkedIDPRequest) (*mgmt_pb.RemoveHumanLinkedIDPResponse, error) {
objectDetails, err := s.command.RemoveHumanExternalIDP(ctx, RemoveHumanLinkedIDPRequestToDomain(ctx, req))
objectDetails, err := s.command.RemoveUserIDPLink(ctx, RemoveHumanLinkedIDPRequestToDomain(ctx, req))
if err != nil {
return nil, err
}

View File

@ -223,8 +223,8 @@ func AddMachineKeyRequestToDomain(req *mgmt_pb.AddMachineKeyRequest) *domain.Mac
}
}
func RemoveHumanLinkedIDPRequestToDomain(ctx context.Context, req *mgmt_pb.RemoveHumanLinkedIDPRequest) *domain.ExternalIDP {
return &domain.ExternalIDP{
func RemoveHumanLinkedIDPRequestToDomain(ctx context.Context, req *mgmt_pb.RemoveHumanLinkedIDPRequest) *domain.UserIDPLink {
return &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: req.UserId,
ResourceOwner: authz.GetCtxData(ctx).OrgID,

View File

@ -233,10 +233,10 @@ func WebAuthNTokenToWebAuthNKeyPb(token *domain.WebAuthNToken) *user_pb.WebAuthN
}
}
func ExternalIDPViewsToExternalIDPs(externalIDPs []*model.ExternalIDPView) []*domain.ExternalIDP {
idps := make([]*domain.ExternalIDP, len(externalIDPs))
func ExternalIDPViewsToExternalIDPs(externalIDPs []*model.ExternalIDPView) []*domain.UserIDPLink {
idps := make([]*domain.UserIDPLink, len(externalIDPs))
for i, idp := range externalIDPs {
idps[i] = &domain.ExternalIDP{
idps[i] = &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: idp.UserID,
ResourceOwner: idp.ResourceOwner,

View File

@ -32,6 +32,6 @@ type AuthRequestRepository interface {
VerifyPasswordless(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
LinkExternalUsers(ctx context.Context, authReqID, userAgentID string, info *domain.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
AutoRegisterExternalUser(ctx context.Context, user *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) error
ResetLinkingUsers(ctx context.Context, authReqID, userAgentID string) error
}

View File

@ -87,7 +87,7 @@ type userEventProvider interface {
}
type userCommandProvider interface {
BulkAddedHumanExternalIDP(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.ExternalIDP) error
BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOwner string, externalIDPs []*domain.UserIDPLink) error
}
type orgViewProvider interface {
@ -238,7 +238,7 @@ func (repo *AuthRequestRepo) CheckExternalUserLogin(ctx context.Context, authReq
return err
}
err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
@ -404,7 +404,7 @@ func (repo *AuthRequestRepo) LinkExternalUsers(ctx context.Context, authReqID, u
if err != nil {
return err
}
err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
@ -422,7 +422,7 @@ func (repo *AuthRequestRepo) ResetLinkingUsers(ctx context.Context, authReqID, u
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) {
func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, registerUser *domain.Human, externalIDP *domain.UserIDPLink, orgMemberRoles []string, authReqID, userAgentID, resourceOwner string, metadatas []*domain.Metadata, info *domain.BrowserInfo) (err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
request, err := repo.getAuthRequest(ctx, authReqID, userAgentID)
@ -437,7 +437,7 @@ func (repo *AuthRequestRepo) AutoRegisterExternalUser(ctx context.Context, regis
request.UserOrgID = human.ResourceOwner
request.SelectedIDPConfigID = externalIDP.IDPConfigID
request.LinkingUsers = nil
err = repo.Command.HumanExternalLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
err = repo.Command.UserIDPLoginChecked(ctx, request.UserOrgID, request.UserID, request.WithCurrentInfo(info))
if err != nil {
return err
}
@ -1093,9 +1093,9 @@ func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider
}
func linkExternalIDPs(ctx context.Context, userCommandProvider userCommandProvider, request *domain.AuthRequest) error {
externalIDPs := make([]*domain.ExternalIDP, len(request.LinkingUsers))
externalIDPs := make([]*domain.UserIDPLink, len(request.LinkingUsers))
for i, linkingUser := range request.LinkingUsers {
externalIDP := &domain.ExternalIDP{
externalIDP := &domain.UserIDPLink{
ObjectRoot: es_models.ObjectRoot{AggregateID: request.UserID},
IDPConfigID: linkingUser.IDPConfigID,
ExternalUserID: linkingUser.ExternalUserID,
@ -1107,7 +1107,7 @@ func linkExternalIDPs(ctx context.Context, userCommandProvider userCommandProvid
UserID: "LOGIN",
OrgID: request.UserOrgID,
}
return userCommandProvider.BulkAddedHumanExternalIDP(authz.SetCtxData(ctx, data), request.UserID, request.UserOrgID, externalIDPs)
return userCommandProvider.BulkAddedUserIDPLinks(authz.SetCtxData(ctx, data), request.UserID, request.UserOrgID, externalIDPs)
}
func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*domain.ExternalUser, idpProviders []*domain.IDPProvider) bool {

View File

@ -145,7 +145,7 @@ func (c *Commands) ReactivateDefaultIDPConfig(ctx context.Context, idpID string)
return writeModelToObjectDetails(&existingIDP.IDPConfigWriteModel.WriteModel), nil
}
func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idpProviders []*domain.IDPProvider, externalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveDefaultIDPConfig(ctx context.Context, idpID string, idpProviders []*domain.IDPProvider, externalIDPs ...*domain.UserIDPLink) (*domain.ObjectDetails, error) {
existingIDP, err := c.iamIDPConfigWriteModelByID(ctx, idpID)
if err != nil {
return nil, err

View File

@ -123,7 +123,7 @@ func (c *Commands) AddIDPProviderToDefaultLoginPolicy(ctx context.Context, idpPr
return writeModelToIDPProvider(&idpModel.IdentityProviderWriteModel), nil
}
func (c *Commands) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.UserIDPLink) (*domain.ObjectDetails, error) {
if !idpProvider.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-66m9s", "Errors.IAM.LoginPolicy.IDP.Invalid")
}
@ -158,7 +158,7 @@ func (c *Commands) RemoveIDPProviderFromDefaultLoginPolicy(ctx context.Context,
return writeModelToObjectDetails(&idpModel.IdentityProviderWriteModel.WriteModel), nil
}
func (c *Commands) removeIDPProviderFromDefaultLoginPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, idpProvider *domain.IDPProvider, cascade bool, cascadeExternalIDPs ...*domain.ExternalIDP) []eventstore.EventPusher {
func (c *Commands) removeIDPProviderFromDefaultLoginPolicy(ctx context.Context, iamAgg *eventstore.Aggregate, idpProvider *domain.IDPProvider, cascade bool, cascadeExternalIDPs ...*domain.UserIDPLink) []eventstore.EventPusher {
var events []eventstore.EventPusher
if cascade {
events = append(events, iam_repo.NewIdentityProviderCascadeRemovedEvent(ctx, iamAgg, idpProvider.IDPConfigID))
@ -167,7 +167,7 @@ func (c *Commands) removeIDPProviderFromDefaultLoginPolicy(ctx context.Context,
}
for _, idp := range cascadeExternalIDPs {
userEvent, _, err := c.removeHumanExternalIDP(ctx, idp, true)
userEvent, _, err := c.removeUserIDPLink(ctx, idp, true)
if err != nil {
logging.LogWithFields("COMMAND-4nfsf", "userid", idp.AggregateID, "idp-id", idp.IDPConfigID).WithError(err).Warn("could not cascade remove externalidp in remove provider from policy")
continue

View File

@ -497,7 +497,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
type args struct {
ctx context.Context
provider *domain.IDPProvider
cascadeExternalIDPs []*domain.ExternalIDP
cascadeExternalIDPs []*domain.UserIDPLink
}
type res struct {
want *domain.ObjectDetails
@ -708,7 +708,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
cascadeExternalIDPs: []*domain.ExternalIDP{
cascadeExternalIDPs: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -751,7 +751,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "", "externaluser1"),
),
@ -764,11 +764,11 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
"config1"),
),
eventFromEventPusher(
user.NewHumanExternalIDPCascadeRemovedEvent(context.Background(),
user.NewUserIDPLinkCascadeRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "externaluser1")),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("config1", "externaluser1")),
uniqueConstraintsFromEventConstraint(user.NewRemoveUserIDPLinkUniqueConstraint("config1", "externaluser1")),
),
),
},
@ -777,7 +777,7 @@ func TestCommandSide_RemoveIDPProviderDefaultLoginPolicy(t *testing.T) {
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
cascadeExternalIDPs: []*domain.ExternalIDP{
cascadeExternalIDPs: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",

View File

@ -153,7 +153,7 @@ func (c *Commands) ReactivateIDPConfig(ctx context.Context, idpID, orgID string)
return writeModelToObjectDetails(&existingIDP.IDPConfigWriteModel.WriteModel), nil
}
func (c *Commands) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.UserIDPLink) (*domain.ObjectDetails, error) {
existingIDP, err := c.orgIDPConfigWriteModelByID(ctx, idpID, orgID)
if err != nil {
return nil, err
@ -173,7 +173,7 @@ func (c *Commands) RemoveIDPConfig(ctx context.Context, idpID, orgID string, cas
return writeModelToObjectDetails(&existingIDP.IDPConfigWriteModel.WriteModel), nil
}
func (c *Commands) removeIDPConfig(ctx context.Context, existingIDP *OrgIDPConfigWriteModel, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.ExternalIDP) ([]eventstore.EventPusher, error) {
func (c *Commands) removeIDPConfig(ctx context.Context, existingIDP *OrgIDPConfigWriteModel, cascadeRemoveProvider bool, cascadeExternalIDPs ...*domain.UserIDPLink) ([]eventstore.EventPusher, error) {
if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified {
return nil, caos_errs.ThrowNotFound(nil, "Org-Yx9vd", "Errors.Org.IDPConfig.NotExisting")
}

View File

@ -426,7 +426,7 @@ func TestCommands_RemoveIDPConfig(t *testing.T) {
idpID string
orgID string
cascadeRemoveProvider bool
cascadeExternalIDPs []*domain.ExternalIDP
cascadeExternalIDPs []*domain.UserIDPLink
}
type res struct {
want *domain.ObjectDetails
@ -531,7 +531,7 @@ func TestCommands_RemoveIDPConfig(t *testing.T) {
),
),
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&org.NewAggregate("user1", "org1").Aggregate,
"idp1",
"name",
@ -550,14 +550,14 @@ func TestCommands_RemoveIDPConfig(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"idp1",
),
user.NewHumanExternalIDPCascadeRemovedEvent(context.Background(),
user.NewUserIDPLinkCascadeRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"idp1",
"id1",
),
),
uniqueConstraintsFromEventConstraint(idpconfig.NewRemoveIDPConfigNameUniqueConstraint("name1", "org1")),
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("idp1", "id1")),
uniqueConstraintsFromEventConstraint(user.NewRemoveUserIDPLinkUniqueConstraint("idp1", "id1")),
),
),
},
@ -566,7 +566,7 @@ func TestCommands_RemoveIDPConfig(t *testing.T) {
"idp1",
"org1",
true,
[]*domain.ExternalIDP{
[]*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",

View File

@ -201,7 +201,7 @@ func (c *Commands) AddIDPProviderToLoginPolicy(ctx context.Context, resourceOwne
return writeModelToIDPProvider(&idpModel.IdentityProviderWriteModel), nil
}
func (c *Commands) RemoveIDPProviderFromLoginPolicy(ctx context.Context, resourceOwner string, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.ExternalIDP) (*domain.ObjectDetails, error) {
func (c *Commands) RemoveIDPProviderFromLoginPolicy(ctx context.Context, resourceOwner string, idpProvider *domain.IDPProvider, cascadeExternalIDPs ...*domain.UserIDPLink) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-M0fs9", "Errors.ResourceOwnerMissing")
}
@ -239,7 +239,7 @@ func (c *Commands) RemoveIDPProviderFromLoginPolicy(ctx context.Context, resourc
return writeModelToObjectDetails(&idpModel.WriteModel), nil
}
func (c *Commands) removeIDPProviderFromLoginPolicy(ctx context.Context, orgAgg *eventstore.Aggregate, idpConfigID string, cascade bool, cascadeExternalIDPs ...*domain.ExternalIDP) []eventstore.EventPusher {
func (c *Commands) removeIDPProviderFromLoginPolicy(ctx context.Context, orgAgg *eventstore.Aggregate, idpConfigID string, cascade bool, cascadeExternalIDPs ...*domain.UserIDPLink) []eventstore.EventPusher {
var events []eventstore.EventPusher
if cascade {
events = append(events, org.NewIdentityProviderCascadeRemovedEvent(ctx, orgAgg, idpConfigID))
@ -248,7 +248,7 @@ func (c *Commands) removeIDPProviderFromLoginPolicy(ctx context.Context, orgAgg
}
for _, idp := range cascadeExternalIDPs {
event, _, err := c.removeHumanExternalIDP(ctx, idp, true)
event, _, err := c.removeUserIDPLink(ctx, idp, true)
if err != nil {
logging.LogWithFields("COMMAND-n8RRf", "userid", idp.AggregateID, "idpconfigid", idp.IDPConfigID).WithError(err).Warn("could not cascade remove external idp")
continue

View File

@ -824,7 +824,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
ctx context.Context
resourceOwner string
provider *domain.IDPProvider
cascadeExternalIDPs []*domain.ExternalIDP
cascadeExternalIDPs []*domain.UserIDPLink
}
type res struct {
want *domain.ObjectDetails
@ -1069,7 +1069,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
Name: "name",
Type: domain.IdentityProviderTypeOrg,
},
cascadeExternalIDPs: []*domain.ExternalIDP{
cascadeExternalIDPs: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -1113,7 +1113,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "", "externaluser1"),
),
@ -1126,11 +1126,11 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
"config1"),
),
eventFromEventPusher(
user.NewHumanExternalIDPCascadeRemovedEvent(context.Background(),
user.NewUserIDPLinkCascadeRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1", "externaluser1")),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("config1", "externaluser1")),
uniqueConstraintsFromEventConstraint(user.NewRemoveUserIDPLinkUniqueConstraint("config1", "externaluser1")),
),
),
},
@ -1140,7 +1140,7 @@ func TestCommandSide_RemoveIDPProviderLoginPolicy(t *testing.T) {
provider: &domain.IDPProvider{
IDPConfigID: "config1",
},
cascadeExternalIDPs: []*domain.ExternalIDP{
cascadeExternalIDPs: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",

View File

@ -131,7 +131,7 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
case *user.UserRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.UniqueUsername)
rm.listRemoveUniqueConstraint(e.Aggregate().ID, user.UniqueExternalIDPType)
rm.listRemoveUniqueConstraint(e.Aggregate().ID, user.UniqueUserIDPLinkType)
case *user.UsernameChangedEvent:
policy, err := rm.commandProvider.getOrgIAMPolicy(rm.ctx, e.Aggregate().ResourceOwner)
if err != nil {
@ -146,12 +146,12 @@ func (rm *UniqueConstraintReadModel) Reduce() error {
continue
}
rm.changeUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, user.NewAddUsernameUniqueConstraint(e.UserName, e.Aggregate().ResourceOwner, policy.UserLoginMustBeDomain))
case *user.HumanExternalIDPAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.NewAddExternalIDPUniqueConstraint(e.IDPConfigID, e.ExternalUserID))
case *user.HumanExternalIDPRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.UniqueExternalIDPType)
case *user.HumanExternalIDPCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.UniqueExternalIDPType)
case *user.UserIDPLinkAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.NewAddUserIDPLinkUniqueConstraint(e.IDPConfigID, e.ExternalUserID))
case *user.UserIDPLinkRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.UniqueUserIDPLinkType)
case *user.UserIDPLinkCascadeRemovedEvent:
rm.removeUniqueConstraint(e.Aggregate().ID, e.IDPConfigID+e.ExternalUserID, user.UniqueUserIDPLinkType)
case *usergrant.UserGrantAddedEvent:
rm.addUniqueConstraint(e.Aggregate().ID, e.Aggregate().ID, usergrant.NewAddUserGrantUniqueConstraint(e.Aggregate().ResourceOwner, e.UserID, e.ProjectID, e.ProjectGrantID))
case *usergrant.UserGrantRemovedEvent:
@ -224,9 +224,9 @@ func (rm *UniqueConstraintReadModel) Query() *eventstore.SearchQueryBuilder {
user.UserUserNameChangedType,
user.UserDomainClaimedType,
user.UserRemovedType,
user.HumanExternalIDPAddedType,
user.HumanExternalIDPRemovedType,
user.HumanExternalIDPCascadeRemovedType,
user.UserIDPLinkAddedType,
user.UserIDPLinkRemovedType,
user.UserIDPLinkCascadeRemovedType,
usergrant.UserGrantAddedType,
usergrant.UserGrantRemovedType,
usergrant.UserGrantCascadeRemovedType,

View File

@ -188,7 +188,7 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
}
var events []eventstore.EventPusher
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.ExternalIDPs, orgIAMPolicy.UserLoginMustBeDomain))
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, orgIAMPolicy.UserLoginMustBeDomain))
for _, grantID := range cascadingGrantIDs {
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)

View File

@ -117,7 +117,7 @@ func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.
return events, humanWriteModel, passwordlessCodeWriteModel, code, nil
}
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, orgMemberRoles []string) (*domain.Human, error) {
func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgMemberRoles []string) (*domain.Human, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
}
@ -129,7 +129,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexity.NotFound")
}
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, externalIDP, orgIAMPolicy, pwPolicy)
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, orgIAMPolicy, pwPolicy)
if err != nil {
return nil, err
}
@ -163,20 +163,20 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if human != nil && human.Username == "" {
human.Username = human.EmailAddress
}
if orgID == "" || !human.IsValid() || externalIDP == nil && (human.Password == nil || human.SecretString == "") {
if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.SecretString == "") {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = false
}
return c.createHuman(ctx, orgID, human, externalIDP, true, false, orgIAMPolicy, pwPolicy)
return c.createHuman(ctx, orgID, human, link, true, false, orgIAMPolicy, pwPolicy)
}
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, externalIDP *domain.ExternalIDP, selfregister, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if err := human.CheckOrgIAMPolicy(orgIAMPolicy); err != nil {
return nil, nil, err
}
@ -216,15 +216,15 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
events = append(events, createAddHumanEvent(ctx, userAgg, human, orgIAMPolicy.UserLoginMustBeDomain))
}
if externalIDP != nil {
event, err := c.addHumanExternalIDP(ctx, userAgg, externalIDP)
if link != nil {
event, err := c.addUserIDPLink(ctx, userAgg, link)
if err != nil {
return nil, nil, err
}
events = append(events, event)
}
if human.IsInitialState(passwordless, externalIDP != nil) {
if human.IsInitialState(passwordless, link != nil) {
initCode, err := domain.NewInitUserCode(c.initializeUserCode)
if err != nil {
return nil, nil, err

View File

@ -1,116 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
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.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
}
events := make([]eventstore.EventPusher, len(externalIDPs))
for i, externalIDP := range externalIDPs {
externalIDPWriteModel := NewHumanExternalIDPWriteModel(userID, externalIDP.IDPConfigID, externalIDP.ExternalUserID, resourceOwner)
userAgg := UserAggregateFromWriteModel(&externalIDPWriteModel.WriteModel)
events[i], err = c.addHumanExternalIDP(ctx, userAgg, externalIDP)
if err != nil {
return err
}
}
_, err = c.eventstore.PushEvents(ctx, events...)
return err
}
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.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, caos_errs.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting")
}
return user.NewHumanExternalIDPAddedEvent(ctx, humanAgg, externalIDP.IDPConfigID, externalIDP.DisplayName, externalIDP.ExternalUserID), nil
}
func (c *Commands) RemoveHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP) (*domain.ObjectDetails, error) {
event, externalIDPWriteModel, err := c.removeHumanExternalIDP(ctx, externalIDP, false)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(externalIDPWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&externalIDPWriteModel.WriteModel), nil
}
func (c *Commands) removeHumanExternalIDP(ctx context.Context, externalIDP *domain.ExternalIDP, cascade bool) (eventstore.EventPusher, *HumanExternalIDPWriteModel, error) {
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)
if err != nil {
return nil, nil, err
}
if existingExternalIDP.State == domain.ExternalIDPStateUnspecified || existingExternalIDP.State == domain.ExternalIDPStateRemoved {
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingExternalIDP.WriteModel)
if cascade {
return user.NewHumanExternalIDPCascadeRemovedEvent(ctx, userAgg, externalIDP.IDPConfigID, externalIDP.ExternalUserID), existingExternalIDP, nil
}
return user.NewHumanExternalIDPRemovedEvent(ctx, userAgg, externalIDP.IDPConfigID, externalIDP.ExternalUserID), existingExternalIDP, nil
}
func (c *Commands) HumanExternalLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-5n8sM", "Errors.IDMissing")
}
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, orgID)
if err != nil {
return err
}
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dn88J", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
_, err = c.eventstore.PushEvents(ctx, user.NewHumanExternalIDPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
func (c *Commands) externalIDPWriteModelByID(ctx context.Context, userID, idpConfigID, externalUserID, resourceOwner string) (writeModel *HumanExternalIDPWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -1425,7 +1425,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
ctx context.Context
orgID string
human *domain.Human
externalIDP *domain.ExternalIDP
link *domain.UserIDPLink
orgMemberRoles []string
}
type res struct {
@ -2134,7 +2134,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
phoneVerificationCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
}
got, err := r.RegisterHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.externalIDP, tt.args.orgMemberRoles)
got, err := r.RegisterHuman(tt.args.ctx, tt.args.orgID, tt.args.human, tt.args.link, tt.args.orgMemberRoles)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@ -0,0 +1,117 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/repository/user"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func (c *Commands) BulkAddedUserIDPLinks(ctx context.Context, userID, resourceOwner string, links []*domain.UserIDPLink) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-03j8f", "Errors.IDMissing")
}
if len(links) == 0 {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ek9s", "Errors.User.ExternalIDP.MinimumExternalIDPNeeded")
}
events := make([]eventstore.EventPusher, len(links))
for i, link := range links {
linkWriteModel := NewUserIDPLinkWriteModel(userID, link.IDPConfigID, link.ExternalUserID, resourceOwner)
userAgg := UserAggregateFromWriteModel(&linkWriteModel.WriteModel)
events[i], err = c.addUserIDPLink(ctx, userAgg, link)
if err != nil {
return err
}
}
_, err = c.eventstore.PushEvents(ctx, events...)
return err
}
func (c *Commands) addUserIDPLink(ctx context.Context, human *eventstore.Aggregate, link *domain.UserIDPLink) (eventstore.EventPusher, error) {
if link.AggregateID != "" && human.ID != link.AggregateID {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-33M0g", "Errors.IDMissing")
}
if !link.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6m9Kd", "Errors.User.ExternalIDP.Invalid")
}
_, err := c.getOrgIDPConfigByID(ctx, link.IDPConfigID, human.ResourceOwner)
if caos_errs.IsNotFound(err) {
_, err = c.getIAMIDPConfigByID(ctx, link.IDPConfigID)
}
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-39nfs", "Errors.IDPConfig.NotExisting")
}
return user.NewUserIDPLinkAddedEvent(ctx, human, link.IDPConfigID, link.DisplayName, link.ExternalUserID), nil
}
func (c *Commands) RemoveUserIDPLink(ctx context.Context, link *domain.UserIDPLink) (*domain.ObjectDetails, error) {
event, linkWriteModel, err := c.removeUserIDPLink(ctx, link, false)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, event)
if err != nil {
return nil, err
}
err = AppendAndReduce(linkWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&linkWriteModel.WriteModel), nil
}
func (c *Commands) removeUserIDPLink(ctx context.Context, link *domain.UserIDPLink, cascade bool) (eventstore.EventPusher, *UserIDPLinkWriteModel, error) {
if !link.IsValid() || link.AggregateID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-3M9ds", "Errors.IDMissing")
}
existingLink, err := c.userIDPLinkWriteModelByID(ctx, link.AggregateID, link.IDPConfigID, link.ExternalUserID, link.ResourceOwner)
if err != nil {
return nil, nil, err
}
if existingLink.State == domain.UserIDPLinkStateUnspecified || existingLink.State == domain.UserIDPLinkStateRemoved {
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-1M9xR", "Errors.User.ExternalIDP.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingLink.WriteModel)
if cascade {
return user.NewUserIDPLinkCascadeRemovedEvent(ctx, userAgg, link.IDPConfigID, link.ExternalUserID), existingLink, nil
}
return user.NewUserIDPLinkRemovedEvent(ctx, userAgg, link.IDPConfigID, link.ExternalUserID), existingLink, nil
}
func (c *Commands) UserIDPLoginChecked(ctx context.Context, orgID, userID string, authRequest *domain.AuthRequest) (err error) {
if userID == "" {
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-5n8sM", "Errors.IDMissing")
}
existingHuman, err := c.getHumanWriteModelByID(ctx, userID, orgID)
if err != nil {
return err
}
if existingHuman.UserState == domain.UserStateUnspecified || existingHuman.UserState == domain.UserStateDeleted {
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-dn88J", "Errors.User.NotFound")
}
userAgg := UserAggregateFromWriteModel(&existingHuman.WriteModel)
_, err = c.eventstore.PushEvents(ctx, user.NewUserIDPCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
return err
}
func (c *Commands) userIDPLinkWriteModelByID(ctx context.Context, userID, idpConfigID, externalUserID, resourceOwner string) (writeModel *UserIDPLinkWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel = NewUserIDPLinkWriteModel(userID, idpConfigID, externalUserID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -6,18 +6,18 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
type HumanExternalIDPWriteModel struct {
type UserIDPLinkWriteModel struct {
eventstore.WriteModel
IDPConfigID string
ExternalUserID string
DisplayName string
State domain.ExternalIDPState
State domain.UserIDPLinkState
}
func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resourceOwner string) *HumanExternalIDPWriteModel {
return &HumanExternalIDPWriteModel{
func NewUserIDPLinkWriteModel(userID, idpConfigID, externalUserID, resourceOwner string) *UserIDPLinkWriteModel {
return &UserIDPLinkWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
@ -27,20 +27,20 @@ func NewHumanExternalIDPWriteModel(userID, idpConfigID, externalUserID, resource
}
}
func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventReader) {
func (wm *UserIDPLinkWriteModel) AppendEvents(events ...eventstore.EventReader) {
for _, event := range events {
switch e := event.(type) {
case *user.HumanExternalIDPAddedEvent:
case *user.UserIDPLinkAddedEvent:
if e.IDPConfigID != wm.IDPConfigID && e.ExternalUserID != wm.ExternalUserID {
continue
}
wm.WriteModel.AppendEvents(e)
case *user.HumanExternalIDPRemovedEvent:
case *user.UserIDPLinkRemovedEvent:
if e.IDPConfigID != wm.IDPConfigID && e.ExternalUserID != wm.ExternalUserID {
continue
}
wm.WriteModel.AppendEvents(e)
case *user.HumanExternalIDPCascadeRemovedEvent:
case *user.UserIDPLinkCascadeRemovedEvent:
if e.IDPConfigID != wm.IDPConfigID && e.ExternalUserID != wm.ExternalUserID {
continue
}
@ -51,34 +51,34 @@ func (wm *HumanExternalIDPWriteModel) AppendEvents(events ...eventstore.EventRea
}
}
func (wm *HumanExternalIDPWriteModel) Reduce() error {
func (wm *UserIDPLinkWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanExternalIDPAddedEvent:
case *user.UserIDPLinkAddedEvent:
wm.IDPConfigID = e.IDPConfigID
wm.DisplayName = e.DisplayName
wm.ExternalUserID = e.ExternalUserID
wm.State = domain.ExternalIDPStateActive
case *user.HumanExternalIDPRemovedEvent:
wm.State = domain.ExternalIDPStateRemoved
case *user.HumanExternalIDPCascadeRemovedEvent:
wm.State = domain.ExternalIDPStateRemoved
wm.State = domain.UserIDPLinkStateActive
case *user.UserIDPLinkRemovedEvent:
wm.State = domain.UserIDPLinkStateRemoved
case *user.UserIDPLinkCascadeRemovedEvent:
wm.State = domain.UserIDPLinkStateRemoved
case *user.UserRemovedEvent:
wm.State = domain.ExternalIDPStateRemoved
wm.State = domain.UserIDPLinkStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *HumanExternalIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *UserIDPLinkWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.HumanExternalIDPAddedType,
user.HumanExternalIDPRemovedType,
user.HumanExternalIDPCascadeRemovedType,
EventTypes(user.UserIDPLinkAddedType,
user.UserIDPLinkRemovedType,
user.UserIDPLinkCascadeRemovedType,
user.UserRemovedType).
Builder()
}

View File

@ -17,7 +17,7 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
func TestCommandSide_BulkAddUserIDPLinks(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
@ -25,7 +25,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx context.Context
userID string
resourceOwner string
externalIDPs []*domain.ExternalIDP
links []*domain.UserIDPLink
}
type res struct {
err func(error) bool
@ -46,7 +46,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
args: args{
ctx: context.Background(),
userID: "",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
@ -85,7 +85,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user2",
@ -110,7 +110,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -137,7 +137,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -171,7 +171,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
@ -179,7 +179,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddExternalIDPUniqueConstraint("config1", "externaluser1")),
uniqueConstraintsFromEventConstraint(user.NewAddUserIDPLinkUniqueConstraint("config1", "externaluser1")),
),
),
},
@ -187,7 +187,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -221,7 +221,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
@ -229,7 +229,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddExternalIDPUniqueConstraint("config1", "externaluser1")),
uniqueConstraintsFromEventConstraint(user.NewAddUserIDPLinkUniqueConstraint("config1", "externaluser1")),
),
),
},
@ -237,7 +237,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
ctx: context.Background(),
userID: "user1",
resourceOwner: "org1",
externalIDPs: []*domain.ExternalIDP{
links: []*domain.UserIDPLink{
{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
@ -256,7 +256,7 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.BulkAddedHumanExternalIDP(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.externalIDPs)
err := r.BulkAddedUserIDPLinks(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.links)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -267,13 +267,13 @@ func TestCommandSide_BulkAddExternalIDPs(t *testing.T) {
}
}
func TestCommandSide_RemoveExternalIDP(t *testing.T) {
func TestCommandSide_RemoveUserIDPLink(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
externalIDP *domain.ExternalIDP
ctx context.Context
link *domain.UserIDPLink
}
type res struct {
want *domain.ObjectDetails
@ -294,7 +294,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
link: &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
@ -315,7 +315,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
link: &domain.UserIDPLink{
IDPConfigID: "config1",
ExternalUserID: "externaluser1",
},
@ -331,7 +331,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
@ -351,7 +351,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
link: &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
@ -373,7 +373,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
link: &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
@ -392,7 +392,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
@ -403,20 +403,20 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPRemovedEvent(context.Background(),
user.NewUserIDPLinkRemovedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"externaluser1",
),
),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("config1", "externaluser1")),
uniqueConstraintsFromEventConstraint(user.NewRemoveUserIDPLinkUniqueConstraint("config1", "externaluser1")),
),
),
},
args: args{
ctx: context.Background(),
externalIDP: &domain.ExternalIDP{
link: &domain.UserIDPLink{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
@ -436,7 +436,7 @@ func TestCommandSide_RemoveExternalIDP(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.RemoveHumanExternalIDP(tt.args.ctx, tt.args.externalIDP)
got, err := r.RemoveUserIDPLink(tt.args.ctx, tt.args.link)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -492,7 +492,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"config1",
"name",
@ -543,7 +543,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
user.NewHumanExternalIDPCheckSucceededEvent(context.Background(),
user.NewUserIDPCheckSucceededEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&user.AuthRequestInfo{
ID: "request1",
@ -574,7 +574,7 @@ func TestCommandSide_ExternalLoginCheck(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.HumanExternalLoginChecked(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.authRequest)
err := r.UserIDPLoginChecked(tt.args.ctx, tt.args.orgID, tt.args.userID, tt.args.authRequest)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@ -13,9 +13,9 @@ import (
type UserWriteModel struct {
eventstore.WriteModel
UserName string
ExternalIDPs []*domain.ExternalIDP
UserState domain.UserState
UserName string
IDPLinks []*domain.UserIDPLink
UserState domain.UserState
}
func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel {
@ -24,7 +24,7 @@ func NewUserWriteModel(userID, resourceOwner string) *UserWriteModel {
AggregateID: userID,
ResourceOwner: resourceOwner,
},
ExternalIDPs: make([]*domain.ExternalIDP, 0),
IDPLinks: make([]*domain.UserIDPLink, 0),
}
}
@ -41,24 +41,24 @@ func (wm *UserWriteModel) Reduce() error {
wm.UserState = domain.UserStateInitial
case *user.HumanInitializedCheckSucceededEvent:
wm.UserState = domain.UserStateActive
case *user.HumanExternalIDPAddedEvent:
wm.ExternalIDPs = append(wm.ExternalIDPs, &domain.ExternalIDP{IDPConfigID: e.IDPConfigID, ExternalUserID: e.ExternalUserID})
case *user.HumanExternalIDPRemovedEvent:
idx, _ := wm.ExternalIDPByID(e.IDPConfigID, e.ExternalUserID)
case *user.UserIDPLinkAddedEvent:
wm.IDPLinks = append(wm.IDPLinks, &domain.UserIDPLink{IDPConfigID: e.IDPConfigID, ExternalUserID: e.ExternalUserID})
case *user.UserIDPLinkRemovedEvent:
idx, _ := wm.IDPLinkByID(e.IDPConfigID, e.ExternalUserID)
if idx < 0 {
continue
}
copy(wm.ExternalIDPs[idx:], wm.ExternalIDPs[idx+1:])
wm.ExternalIDPs[len(wm.ExternalIDPs)-1] = nil
wm.ExternalIDPs = wm.ExternalIDPs[:len(wm.ExternalIDPs)-1]
case *user.HumanExternalIDPCascadeRemovedEvent:
idx, _ := wm.ExternalIDPByID(e.IDPConfigID, e.ExternalUserID)
copy(wm.IDPLinks[idx:], wm.IDPLinks[idx+1:])
wm.IDPLinks[len(wm.IDPLinks)-1] = nil
wm.IDPLinks = wm.IDPLinks[:len(wm.IDPLinks)-1]
case *user.UserIDPLinkCascadeRemovedEvent:
idx, _ := wm.IDPLinkByID(e.IDPConfigID, e.ExternalUserID)
if idx < 0 {
continue
}
copy(wm.ExternalIDPs[idx:], wm.ExternalIDPs[idx+1:])
wm.ExternalIDPs[len(wm.ExternalIDPs)-1] = nil
wm.ExternalIDPs = wm.ExternalIDPs[:len(wm.ExternalIDPs)-1]
copy(wm.IDPLinks[idx:], wm.IDPLinks[idx+1:])
wm.IDPLinks[len(wm.IDPLinks)-1] = nil
wm.IDPLinks = wm.IDPLinks[:len(wm.IDPLinks)-1]
case *user.MachineAddedEvent:
wm.UserName = e.UserName
wm.UserState = domain.UserStateActive
@ -96,9 +96,9 @@ func (wm *UserWriteModel) Query() *eventstore.SearchQueryBuilder {
user.HumanAddedType,
user.HumanRegisteredType,
user.HumanInitializedCheckSucceededType,
user.HumanExternalIDPAddedType,
user.HumanExternalIDPRemovedType,
user.HumanExternalIDPCascadeRemovedType,
user.UserIDPLinkAddedType,
user.UserIDPLinkRemovedType,
user.UserIDPLinkCascadeRemovedType,
user.MachineAddedEventType,
user.UserUserNameChangedType,
user.MachineChangedEventType,
@ -149,8 +149,8 @@ func hasUserState(check domain.UserState, states ...domain.UserState) bool {
return false
}
func (wm *UserWriteModel) ExternalIDPByID(idpID, externalUserID string) (idx int, idp *domain.ExternalIDP) {
for idx, idp = range wm.ExternalIDPs {
func (wm *UserWriteModel) IDPLinkByID(idpID, externalUserID string) (idx int, idp *domain.UserIDPLink) {
for idx, idp = range wm.IDPLinks {
if idp.IDPConfigID == idpID && idp.ExternalUserID == externalUserID {
return idx, idp
}

View File

@ -1078,7 +1078,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
),
),
eventFromEventPusher(
user.NewHumanExternalIDPAddedEvent(context.Background(),
user.NewUserIDPLinkAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"idpConfigID",
"displayName",
@ -1107,7 +1107,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
),
},
uniqueConstraintsFromEventConstraint(user.NewRemoveUsernameUniqueConstraint("username", "org1", true)),
uniqueConstraintsFromEventConstraint(user.NewRemoveExternalIDPUniqueConstraint("idpConfigID", "externalUserID")),
uniqueConstraintsFromEventConstraint(user.NewRemoveUserIDPLinkUniqueConstraint("idpConfigID", "externalUserID")),
),
),
},

View File

@ -1,29 +0,0 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
type ExternalIDP struct {
es_models.ObjectRoot
IDPConfigID string
ExternalUserID string
DisplayName string
}
func (idp *ExternalIDP) IsValid() bool {
return idp.IDPConfigID != "" && idp.ExternalUserID != ""
}
type ExternalIDPState int32
const (
ExternalIDPStateUnspecified ExternalIDPState = iota
ExternalIDPStateActive
ExternalIDPStateRemoved
externalIDPStateCount
)
func (s ExternalIDPState) Valid() bool {
return s >= 0 && s < externalIDPStateCount
}

View File

@ -0,0 +1,29 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
type UserIDPLink struct {
es_models.ObjectRoot
IDPConfigID string
ExternalUserID string
DisplayName string
}
func (idp *UserIDPLink) IsValid() bool {
return idp.IDPConfigID != "" && idp.ExternalUserID != ""
}
type UserIDPLinkState int32
const (
UserIDPLinkStateUnspecified UserIDPLinkState = iota
UserIDPLinkStateActive
UserIDPLinkStateRemoved
userIDPLinkStateCount
)
func (s UserIDPLinkState) Valid() bool {
return s >= 0 && s < userIDPLinkStateCount
}

View File

@ -64,15 +64,15 @@ func assertReduce(t *testing.T, stmt *handler.Statement, err error, want wantRed
return
}
if stmt.AggregateType != want.aggregateType {
t.Errorf("wront aggregate type: want: %q got: %q", want.aggregateType, stmt.AggregateType)
t.Errorf("wrong aggregate type: want: %q got: %q", want.aggregateType, stmt.AggregateType)
}
if stmt.PreviousSequence != want.previousSequence {
t.Errorf("wront previous sequence: want: %d got: %d", want.previousSequence, stmt.PreviousSequence)
t.Errorf("wrong previous sequence: want: %d got: %d", want.previousSequence, stmt.PreviousSequence)
}
if stmt.Sequence != want.sequence {
t.Errorf("wront sequence: want: %d got: %d", want.sequence, stmt.Sequence)
t.Errorf("wrong sequence: want: %d got: %d", want.sequence, stmt.Sequence)
}
if stmt.Execute == nil {
want.executer.Validate(t)

View File

@ -0,0 +1,111 @@
package projection
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
"github.com/caos/zitadel/internal/repository/user"
)
type IDPUserLinkProjection struct {
crdb.StatementHandler
}
const (
IDPUserLinkTable = "zitadel.projections.idp_user_links"
)
func NewIDPUserLinkProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IDPUserLinkProjection {
p := &IDPUserLinkProjection{}
config.ProjectionName = IDPUserLinkTable
config.Reducers = p.reducers()
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
return p
}
func (p *IDPUserLinkProjection) reducers() []handler.AggregateReducer {
return []handler.AggregateReducer{
{
Aggregate: user.AggregateType,
EventRedusers: []handler.EventReducer{
{
Event: user.UserIDPLinkAddedType,
Reduce: p.reduceAdded,
},
{
Event: user.UserIDPLinkCascadeRemovedType,
Reduce: p.reduceCascadeRemoved,
},
{
Event: user.UserIDPLinkRemovedType,
Reduce: p.reduceRemoved,
},
},
},
}
}
const (
IDPUserLinkIDPIDCol = "idp_id"
IDPUserLinkUserIDCol = "user_id"
IDPUserLinkExternalUserIDCol = "external_user_id"
IDPUserLinkCreationDateCol = "creation_date"
IDPUserLinkChangeDateCol = "change_date"
IDPUserLinkSequenceCol = "sequence"
IDPUserLinkResourceOwnerCol = "resource_owner"
IDPUserLinkDisplayNameCol = "display_name"
)
func (p *IDPUserLinkProjection) reduceAdded(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*user.UserIDPLinkAddedEvent)
if !ok {
logging.LogWithFields("HANDL-v2qC3", "seq", event.Sequence(), "expectedType", user.UserIDPLinkAddedType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-DpmXq", "reduce.wrong.event.type")
}
return crdb.NewCreateStatement(e,
[]handler.Column{
handler.NewCol(IDPUserLinkIDPIDCol, e.IDPConfigID),
handler.NewCol(IDPUserLinkUserIDCol, e.Aggregate().ID),
handler.NewCol(IDPUserLinkExternalUserIDCol, e.ExternalUserID),
handler.NewCol(IDPUserLinkCreationDateCol, e.CreationDate()),
handler.NewCol(IDPUserLinkChangeDateCol, e.CreationDate()),
handler.NewCol(IDPUserLinkSequenceCol, e.Sequence()),
handler.NewCol(IDPUserLinkResourceOwnerCol, e.Aggregate().ResourceOwner),
handler.NewCol(IDPUserLinkDisplayNameCol, e.DisplayName),
},
), nil
}
func (p *IDPUserLinkProjection) reduceRemoved(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*user.UserIDPLinkRemovedEvent)
if !ok {
logging.LogWithFields("HANDL-zX5m9", "seq", event.Sequence(), "expectedType", user.UserIDPLinkRemovedType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-AZmfJ", "reduce.wrong.event.type")
}
return crdb.NewDeleteStatement(e,
[]handler.Condition{
handler.NewCond(IDPUserLinkIDPIDCol, e.IDPConfigID),
handler.NewCond(IDPUserLinkUserIDCol, e.Aggregate().ID),
handler.NewCond(IDPUserLinkExternalUserIDCol, e.ExternalUserID),
},
), nil
}
func (p *IDPUserLinkProjection) reduceCascadeRemoved(event eventstore.EventReader) (*handler.Statement, error) {
e, ok := event.(*user.UserIDPLinkCascadeRemovedEvent)
if !ok {
logging.LogWithFields("HANDL-I0s2H", "seq", event.Sequence(), "expectedType", user.UserIDPLinkCascadeRemovedType).Error("wrong event type")
return nil, errors.ThrowInvalidArgument(nil, "HANDL-jQpv9", "reduce.wrong.event.type")
}
return crdb.NewDeleteStatement(e,
[]handler.Condition{
handler.NewCond(IDPUserLinkIDPIDCol, e.IDPConfigID),
handler.NewCond(IDPUserLinkUserIDCol, e.Aggregate().ID),
handler.NewCond(IDPUserLinkExternalUserIDCol, e.ExternalUserID),
},
), nil
}

View File

@ -0,0 +1,139 @@
package projection
import (
"testing"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler"
"github.com/caos/zitadel/internal/eventstore/repository"
"github.com/caos/zitadel/internal/repository/user"
)
func TestIDPUserLinkProjection_reduces(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.EventReader
}
tests := []struct {
name string
args args
reduce func(event eventstore.EventReader) (*handler.Statement, error)
want wantReduce
}{
{
name: "reduceAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(user.UserIDPLinkAddedType),
user.AggregateType,
[]byte(`{
"idpConfigId": "idp-config-id",
"userId": "external-user-id",
"displayName": "gigi@caos.ch"
}`),
), user.UserIDPLinkAddedEventMapper),
},
reduce: (&IDPUserLinkProjection{}).reduceAdded,
want: wantReduce{
aggregateType: user.AggregateType,
sequence: 15,
previousSequence: 10,
projection: IDPUserLinkTable,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO zitadel.projections.idp_user_links (idp_id, user_id, external_user_id, creation_date, change_date, sequence, resource_owner, display_name) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"idp-config-id",
"agg-id",
"external-user-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"gigi@caos.ch",
},
},
},
},
},
},
{
name: "reduceRemoved",
args: args{
event: getEvent(testEvent(
repository.EventType(user.UserIDPLinkRemovedType),
user.AggregateType,
[]byte(`{
"idpConfigId": "idp-config-id",
"userId": "external-user-id"
}`),
), user.UserIDPLinkRemovedEventMapper),
},
reduce: (&IDPUserLinkProjection{}).reduceRemoved,
want: wantReduce{
aggregateType: user.AggregateType,
sequence: 15,
previousSequence: 10,
projection: IDPUserLinkTable,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (idp_id = $1) AND (user_id = $2) AND (external_user_id = $3)",
expectedArgs: []interface{}{
"idp-config-id",
"agg-id",
"external-user-id",
},
},
},
},
},
},
{
name: "reduceCascadeRemoved",
args: args{
event: getEvent(testEvent(
repository.EventType(user.UserIDPLinkCascadeRemovedType),
user.AggregateType,
[]byte(`{
"idpConfigId": "idp-config-id",
"userId": "external-user-id"
}`),
), user.UserIDPLinkCascadeRemovedEventMapper),
},
reduce: (&IDPUserLinkProjection{}).reduceCascadeRemoved,
want: wantReduce{
aggregateType: user.AggregateType,
sequence: 15,
previousSequence: 10,
projection: IDPUserLinkTable,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM zitadel.projections.idp_user_links WHERE (idp_id = $1) AND (user_id = $2) AND (external_user_id = $3)",
expectedArgs: []interface{}{
"idp-config-id",
"agg-id",
"external-user-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t)
got, err := tt.reduce(event)
if _, ok := err.(errors.InvalidArgument); !ok {
t.Errorf("no wrong event mapping: %v, got: %v", err, got)
}
event = tt.args.event(t)
got, err = tt.reduce(event)
assertReduce(t, got, err, tt.want)
})
}
}

View File

@ -49,6 +49,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
NewLoginPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["login_policies"]))
NewIDPProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["idps"]))
NewAppProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["apps"]))
NewIDPUserLinkProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["idp_user_links"]))
NewMailTemplateProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["mail_templates"]))
NewMessageTextProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["message_texts"]))
NewCustomTextProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["custom_texts"]))

View File

@ -60,10 +60,10 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(HumanPasswordCodeSentType, HumanPasswordCodeSentEventMapper).
RegisterFilterEventMapper(HumanPasswordCheckSucceededType, HumanPasswordCheckSucceededEventMapper).
RegisterFilterEventMapper(HumanPasswordCheckFailedType, HumanPasswordCheckFailedEventMapper).
RegisterFilterEventMapper(HumanExternalIDPAddedType, HumanExternalIDPAddedEventMapper).
RegisterFilterEventMapper(HumanExternalIDPRemovedType, HumanExternalIDPRemovedEventMapper).
RegisterFilterEventMapper(HumanExternalIDPCascadeRemovedType, HumanExternalIDPCascadeRemovedEventMapper).
RegisterFilterEventMapper(HumanExternalLoginCheckSucceededType, HumanExternalIDPCheckSucceededEventMapper).
RegisterFilterEventMapper(UserIDPLinkAddedType, UserIDPLinkAddedEventMapper).
RegisterFilterEventMapper(UserIDPLinkRemovedType, UserIDPLinkRemovedEventMapper).
RegisterFilterEventMapper(UserIDPLinkCascadeRemovedType, UserIDPLinkCascadeRemovedEventMapper).
RegisterFilterEventMapper(UserIDPLoginCheckSucceededType, UserIDPCheckSucceededEventMapper).
RegisterFilterEventMapper(HumanEmailChangedType, HumanEmailChangedEventMapper).
RegisterFilterEventMapper(HumanEmailVerifiedType, HumanEmailVerifiedEventMapper).
RegisterFilterEventMapper(HumanEmailVerificationFailedType, HumanEmailVerificationFailedEventMapper).

View File

@ -3,6 +3,7 @@ package user
import (
"context"
"encoding/json"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/errors"
@ -10,31 +11,31 @@ import (
)
const (
UniqueExternalIDPType = "external_idps"
externalIDPEventPrefix = humanEventPrefix + "externalidp."
externalLoginEventPrefix = humanEventPrefix + "externallogin."
UniqueUserIDPLinkType = "external_idps"
UserIDPLinkEventPrefix = humanEventPrefix + "externalidp."
idpLoginEventPrefix = humanEventPrefix + "externallogin."
HumanExternalIDPAddedType = externalIDPEventPrefix + "added"
HumanExternalIDPRemovedType = externalIDPEventPrefix + "removed"
HumanExternalIDPCascadeRemovedType = externalIDPEventPrefix + "cascade.removed"
UserIDPLinkAddedType = UserIDPLinkEventPrefix + "added"
UserIDPLinkRemovedType = UserIDPLinkEventPrefix + "removed"
UserIDPLinkCascadeRemovedType = UserIDPLinkEventPrefix + "cascade.removed"
HumanExternalLoginCheckSucceededType = externalLoginEventPrefix + "check.succeeded"
UserIDPLoginCheckSucceededType = idpLoginEventPrefix + "check.succeeded"
)
func NewAddExternalIDPUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint {
func NewAddUserIDPLinkUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint {
return eventstore.NewAddEventUniqueConstraint(
UniqueExternalIDPType,
UniqueUserIDPLinkType,
idpConfigID+externalUserID,
"Errors.User.ExternalIDP.AlreadyExists")
}
func NewRemoveExternalIDPUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint {
func NewRemoveUserIDPLinkUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint {
return eventstore.NewRemoveEventUniqueConstraint(
UniqueExternalIDPType,
UniqueUserIDPLinkType,
idpConfigID+externalUserID)
}
type HumanExternalIDPAddedEvent struct {
type UserIDPLinkAddedEvent struct {
eventstore.BaseEvent `json:"-"`
IDPConfigID string `json:"idpConfigId,omitempty"`
@ -42,26 +43,26 @@ type HumanExternalIDPAddedEvent struct {
DisplayName string `json:"displayName,omitempty"`
}
func (e *HumanExternalIDPAddedEvent) Data() interface{} {
func (e *UserIDPLinkAddedEvent) Data() interface{} {
return e
}
func (e *HumanExternalIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddExternalIDPUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
func (e *UserIDPLinkAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewAddUserIDPLinkUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
}
func NewHumanExternalIDPAddedEvent(
func NewUserIDPLinkAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
idpConfigID,
displayName,
externalUserID string,
) *HumanExternalIDPAddedEvent {
return &HumanExternalIDPAddedEvent{
) *UserIDPLinkAddedEvent {
return &UserIDPLinkAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanExternalIDPAddedType,
UserIDPLinkAddedType,
),
IDPConfigID: idpConfigID,
DisplayName: displayName,
@ -69,8 +70,8 @@ func NewHumanExternalIDPAddedEvent(
}
}
func HumanExternalIDPAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &HumanExternalIDPAddedEvent{
func UserIDPLinkAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &UserIDPLinkAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@ -82,40 +83,40 @@ func HumanExternalIDPAddedEventMapper(event *repository.Event) (eventstore.Event
return e, nil
}
type HumanExternalIDPRemovedEvent struct {
type UserIDPLinkRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
IDPConfigID string `json:"idpConfigId"`
ExternalUserID string `json:"userId,omitempty"`
}
func (e *HumanExternalIDPRemovedEvent) Data() interface{} {
func (e *UserIDPLinkRemovedEvent) Data() interface{} {
return e
}
func (e *HumanExternalIDPRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveExternalIDPUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
func (e *UserIDPLinkRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveUserIDPLinkUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
}
func NewHumanExternalIDPRemovedEvent(
func NewUserIDPLinkRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
idpConfigID,
externalUserID string,
) *HumanExternalIDPRemovedEvent {
return &HumanExternalIDPRemovedEvent{
) *UserIDPLinkRemovedEvent {
return &UserIDPLinkRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanExternalIDPRemovedType,
UserIDPLinkRemovedType,
),
IDPConfigID: idpConfigID,
ExternalUserID: externalUserID,
}
}
func HumanExternalIDPRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &HumanExternalIDPRemovedEvent{
func UserIDPLinkRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &UserIDPLinkRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
@ -127,86 +128,86 @@ func HumanExternalIDPRemovedEventMapper(event *repository.Event) (eventstore.Eve
return e, nil
}
type HumanExternalIDPCascadeRemovedEvent struct {
type UserIDPLinkCascadeRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
IDPConfigID string `json:"idpConfigId"`
ExternalUserID string `json:"userId,omitempty"`
}
func (e *HumanExternalIDPCascadeRemovedEvent) Data() interface{} {
func (e *UserIDPLinkCascadeRemovedEvent) Data() interface{} {
return e
}
func (e *HumanExternalIDPCascadeRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveExternalIDPUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
func (e *UserIDPLinkCascadeRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return []*eventstore.EventUniqueConstraint{NewRemoveUserIDPLinkUniqueConstraint(e.IDPConfigID, e.ExternalUserID)}
}
func NewHumanExternalIDPCascadeRemovedEvent(
func NewUserIDPLinkCascadeRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
idpConfigID,
externalUserID string,
) *HumanExternalIDPCascadeRemovedEvent {
return &HumanExternalIDPCascadeRemovedEvent{
) *UserIDPLinkCascadeRemovedEvent {
return &UserIDPLinkCascadeRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanExternalIDPCascadeRemovedType,
UserIDPLinkCascadeRemovedType,
),
IDPConfigID: idpConfigID,
ExternalUserID: externalUserID,
}
}
func HumanExternalIDPCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &HumanExternalIDPCascadeRemovedEvent{
func UserIDPLinkCascadeRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &UserIDPLinkCascadeRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "USER-2M0sd", "unable to unmarshal user external idp cascade removed")
return nil, errors.ThrowInternal(err, "USER-dKGqO", "unable to unmarshal user external idp cascade removed")
}
return e, nil
}
type HumanExternalIDPCheckSucceededEvent struct {
type UserIDPCheckSucceededEvent struct {
eventstore.BaseEvent `json:"-"`
*AuthRequestInfo
}
func (e *HumanExternalIDPCheckSucceededEvent) Data() interface{} {
func (e *UserIDPCheckSucceededEvent) Data() interface{} {
return e
}
func (e *HumanExternalIDPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
func (e *UserIDPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewHumanExternalIDPCheckSucceededEvent(
func NewUserIDPCheckSucceededEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
info *AuthRequestInfo) *HumanExternalIDPCheckSucceededEvent {
return &HumanExternalIDPCheckSucceededEvent{
info *AuthRequestInfo) *UserIDPCheckSucceededEvent {
return &UserIDPCheckSucceededEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
HumanExternalLoginCheckSucceededType,
UserIDPLoginCheckSucceededType,
),
AuthRequestInfo: info,
}
}
func HumanExternalIDPCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &HumanExternalIDPCheckSucceededEvent{
func UserIDPCheckSucceededEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &UserIDPCheckSucceededEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "USER-2M0sd", "unable to unmarshal user external idp check succeeded")
return nil, errors.ThrowInternal(err, "USER-oikSS", "unable to unmarshal user external idp check succeeded")
}
return e, nil

View File

@ -163,7 +163,7 @@ type UserRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
userName string
externalIDPs []*domain.ExternalIDP
externalIDPs []*domain.UserIDPLink
loginMustBeDomain bool
}
@ -177,7 +177,7 @@ func (e *UserRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstrai
events = append(events, NewRemoveUsernameUniqueConstraint(e.userName, e.Aggregate().ResourceOwner, e.loginMustBeDomain))
}
for _, idp := range e.externalIDPs {
events = append(events, NewRemoveExternalIDPUniqueConstraint(idp.IDPConfigID, idp.ExternalUserID))
events = append(events, NewRemoveUserIDPLinkUniqueConstraint(idp.IDPConfigID, idp.ExternalUserID))
}
return events
}
@ -186,7 +186,7 @@ func NewUserRemovedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
userName string,
externalIDPs []*domain.ExternalIDP,
externalIDPs []*domain.UserIDPLink,
userLoginMustBeDomain bool,
) *UserRemovedEvent {
return &UserRemovedEvent{

View File

@ -348,7 +348,7 @@ func (l *Login) mapTokenToLoginUser(tokens *oidc.Tokens, idpConfig *iam_model.ID
}
return externalUser
}
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *query.OrgIAMPolicy, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP, []*domain.Metadata) {
func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *query.OrgIAMPolicy, linkingUser *domain.ExternalUser, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.UserIDPLink, []*domain.Metadata) {
username := linkingUser.PreferredUsername
switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail:
@ -398,7 +398,7 @@ func (l *Login) mapExternalUserToLoginUser(orgIamPolicy *query.OrgIAMPolicy, lin
displayName = linkingUser.Email
}
externalIDP := &domain.ExternalIDP{
externalIDP := &domain.UserIDPLink{
IDPConfigID: idpConfig.IDPConfigID,
ExternalUserID: linkingUser.ExternalUserID,
DisplayName: displayName,

View File

@ -130,7 +130,7 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
l.registerExternalUser(w, r, authReq, iam, user, externalIDP)
}
func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.ExternalIDP) {
func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.UserIDPLink) {
resourceOwner := iam.GlobalOrgID
memberRoles := []string{domain.RoleOrgProjectCreator}
@ -146,7 +146,7 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut
l.renderNextStep(w, r, authReq)
}
func (l *Login) renderExternalRegisterOverview(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.OrgIAMPolicy, human *domain.Human, idp *domain.ExternalIDP, err error) {
func (l *Login) renderExternalRegisterOverview(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, orgIAMPolicy *query.OrgIAMPolicy, human *domain.Human, idp *domain.UserIDPLink, err error) {
var errID, errMessage string
if err != nil {
errID, errMessage = l.getErrorMessage(r, err)
@ -216,7 +216,7 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque
l.renderNextStep(w, r, authReq)
}
func (l *Login) mapTokenToLoginHumanAndExternalIDP(orgIamPolicy *query.OrgIAMPolicy, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.ExternalIDP) {
func (l *Login) mapTokenToLoginHumanAndExternalIDP(orgIamPolicy *query.OrgIAMPolicy, tokens *oidc.Tokens, idpConfig *iam_model.IDPConfigView) (*domain.Human, *domain.UserIDPLink) {
username := tokens.IDTokenClaims.GetPreferredUsername()
switch idpConfig.OIDCUsernameMapping {
case iam_model.OIDCMappingFieldEmail:
@ -264,7 +264,7 @@ func (l *Login) mapTokenToLoginHumanAndExternalIDP(orgIamPolicy *query.OrgIAMPol
displayName = tokens.IDTokenClaims.GetEmail()
}
externalIDP := &domain.ExternalIDP{
externalIDP := &domain.UserIDPLink{
IDPConfigID: idpConfig.IDPConfigID,
ExternalUserID: tokens.IDTokenClaims.GetSubject(),
DisplayName: displayName,
@ -304,8 +304,8 @@ func (l *Login) mapExternalRegisterDataToUser(r *http.Request, data *externalReg
return human, nil
}
func (l *Login) getExternalIDP(data *externalRegisterFormData) (*domain.ExternalIDP, error) {
return &domain.ExternalIDP{
func (l *Login) getExternalIDP(data *externalRegisterFormData) (*domain.UserIDPLink, error) {
return &domain.UserIDPLink{
IDPConfigID: data.ExternalIDPConfigID,
ExternalUserID: data.ExternalIDPExtUserID,
DisplayName: data.ExternalIDPDisplayName,

View File

@ -0,0 +1,14 @@
CREATE TABLE zitadel.projections.idp_user_links(
idp_id STRING,
user_id STRING,
external_user_id STRING,
display_name STRING,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
sequence INT8,
resource_owner STRING,
PRIMARY KEY (idp_id, external_user_id),
INDEX idx_user (user_id)
);