mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 22:07:32 +00:00

# Which Problems Are Solved Currently the username uniqueness is on instance level, we want to achieve a way to set it at organization level. # How the Problems Are Solved Addition of endpoints and a resource on organization level, where this setting can be managed. If nothing it set, the uniqueness is expected to be at instance level, where only users with instance permissions should be able to change this setting. # Additional Changes None # Additional Context Includes #10086 Closes #9964 --------- Co-authored-by: Marco A. <marco@zitadel.com>
191 lines
5.9 KiB
Go
191 lines
5.9 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/repository/org"
|
|
"github.com/zitadel/zitadel/internal/repository/policy"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
)
|
|
|
|
type PolicyDomainWriteModel struct {
|
|
eventstore.WriteModel
|
|
|
|
UserLoginMustBeDomain bool
|
|
ValidateOrgDomains bool
|
|
SMTPSenderAddressMatchesInstanceDomain bool
|
|
State domain.PolicyState
|
|
}
|
|
|
|
func (wm *PolicyDomainWriteModel) Reduce() error {
|
|
for _, event := range wm.Events {
|
|
switch e := event.(type) {
|
|
case *policy.DomainPolicyAddedEvent:
|
|
wm.UserLoginMustBeDomain = e.UserLoginMustBeDomain
|
|
wm.ValidateOrgDomains = e.ValidateOrgDomains
|
|
wm.SMTPSenderAddressMatchesInstanceDomain = e.SMTPSenderAddressMatchesInstanceDomain
|
|
wm.State = domain.PolicyStateActive
|
|
case *policy.DomainPolicyChangedEvent:
|
|
if e.UserLoginMustBeDomain != nil {
|
|
wm.UserLoginMustBeDomain = *e.UserLoginMustBeDomain
|
|
}
|
|
if e.ValidateOrgDomains != nil {
|
|
wm.ValidateOrgDomains = *e.ValidateOrgDomains
|
|
}
|
|
if e.SMTPSenderAddressMatchesInstanceDomain != nil {
|
|
wm.SMTPSenderAddressMatchesInstanceDomain = *e.SMTPSenderAddressMatchesInstanceDomain
|
|
}
|
|
case *policy.DomainPolicyRemovedEvent:
|
|
wm.State = domain.PolicyStateRemoved
|
|
}
|
|
}
|
|
return wm.WriteModel.Reduce()
|
|
}
|
|
|
|
type DomainPolicyUsernamesWriteModel struct {
|
|
eventstore.WriteModel
|
|
|
|
PrimaryDomain string
|
|
VerifiedDomains []string
|
|
Users []*domainPolicyUsers
|
|
}
|
|
|
|
type domainPolicyUsers struct {
|
|
id string
|
|
username string
|
|
}
|
|
|
|
func NewDomainPolicyUsernamesWriteModel(orgID string) *DomainPolicyUsernamesWriteModel {
|
|
return &DomainPolicyUsernamesWriteModel{
|
|
WriteModel: eventstore.WriteModel{
|
|
ResourceOwner: orgID,
|
|
},
|
|
Users: make([]*domainPolicyUsers, 0),
|
|
}
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) AppendEvents(events ...eventstore.Event) {
|
|
wm.WriteModel.AppendEvents(events...)
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) Reduce() error {
|
|
for _, event := range wm.Events {
|
|
switch e := event.(type) {
|
|
case *org.DomainVerifiedEvent:
|
|
wm.VerifiedDomains = append(wm.VerifiedDomains, e.Domain)
|
|
case *org.DomainRemovedEvent:
|
|
wm.removeDomain(e.Domain)
|
|
case *org.DomainPrimarySetEvent:
|
|
wm.PrimaryDomain = e.Domain
|
|
case *user.HumanAddedEvent:
|
|
wm.Users = append(wm.Users, &domainPolicyUsers{id: e.Aggregate().ID, username: e.UserName})
|
|
case *user.HumanRegisteredEvent:
|
|
wm.Users = append(wm.Users, &domainPolicyUsers{id: e.Aggregate().ID, username: e.UserName})
|
|
case *user.MachineAddedEvent:
|
|
wm.Users = append(wm.Users, &domainPolicyUsers{id: e.Aggregate().ID, username: e.UserName})
|
|
case *user.UsernameChangedEvent:
|
|
for _, user := range wm.Users {
|
|
if user.id == e.Aggregate().ID {
|
|
user.username = e.UserName
|
|
break
|
|
}
|
|
}
|
|
case *user.DomainClaimedEvent:
|
|
for _, user := range wm.Users {
|
|
if user.id == e.Aggregate().ID {
|
|
user.username = e.UserName
|
|
break
|
|
}
|
|
}
|
|
case *user.UserRemovedEvent:
|
|
wm.removeUser(e.Aggregate().ID)
|
|
}
|
|
}
|
|
return wm.WriteModel.Reduce()
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) removeDomain(domain string) {
|
|
for i, verifiedDomain := range wm.VerifiedDomains {
|
|
if verifiedDomain == domain {
|
|
wm.VerifiedDomains[i] = wm.VerifiedDomains[len(wm.VerifiedDomains)-1]
|
|
wm.VerifiedDomains[len(wm.VerifiedDomains)-1] = ""
|
|
wm.VerifiedDomains = wm.VerifiedDomains[:len(wm.VerifiedDomains)-1]
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) removeUser(userID string) {
|
|
for i, user := range wm.Users {
|
|
if user.id == userID {
|
|
wm.Users[i] = wm.Users[len(wm.Users)-1]
|
|
wm.Users[len(wm.Users)-1] = nil
|
|
wm.Users = wm.Users[:len(wm.Users)-1]
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
|
ResourceOwner(wm.ResourceOwner).
|
|
AddQuery().
|
|
AggregateTypes(org.AggregateType, user.AggregateType).
|
|
EventTypes(
|
|
org.OrgDomainVerifiedEventType,
|
|
org.OrgDomainRemovedEventType,
|
|
org.OrgDomainPrimarySetEventType,
|
|
user.HumanAddedType,
|
|
user.HumanRegisteredType,
|
|
user.MachineAddedEventType,
|
|
user.UserUserNameChangedType,
|
|
user.UserDomainClaimedType,
|
|
user.UserRemovedType,
|
|
).
|
|
Builder()
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) NewUsernameChangedEvents(ctx context.Context, userLoginMustBeDomain, organizationScopedUsernames, oldUserLoginMustBeDomain bool) []eventstore.Command {
|
|
events := make([]eventstore.Command, 0, len(wm.Users))
|
|
for _, changeUser := range wm.Users {
|
|
events = append(events, user.NewUsernameChangedEvent(ctx,
|
|
&user.NewAggregate(changeUser.id, wm.ResourceOwner).Aggregate,
|
|
changeUser.username,
|
|
wm.newUsername(changeUser.username, userLoginMustBeDomain),
|
|
userLoginMustBeDomain,
|
|
organizationScopedUsernames,
|
|
user.UsernameChangedEventWithPolicyChange(oldUserLoginMustBeDomain),
|
|
))
|
|
}
|
|
return events
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) Usernames() []string {
|
|
usernames := make([]string, 0, len(wm.Users))
|
|
for i, user := range wm.Users {
|
|
usernames[i] = user.username
|
|
}
|
|
return usernames
|
|
}
|
|
|
|
func (wm *DomainPolicyUsernamesWriteModel) newUsername(username string, userLoginMustBeDomain bool) string {
|
|
if !userLoginMustBeDomain {
|
|
// if the UserLoginMustBeDomain will be false, then it's currently true
|
|
// which means the usernames must be suffixed to ensure their uniqueness
|
|
// and the preferred login name remains the same
|
|
return username + "@" + wm.PrimaryDomain
|
|
}
|
|
// the UserLoginMustBeDomain is currently false
|
|
// which means the usernames might already be suffixed by a verified domain
|
|
// so let's remove a potential duplicate suffix
|
|
for _, verifiedDomain := range wm.VerifiedDomains {
|
|
if index := strings.LastIndex(username, "@"+verifiedDomain); index > 0 {
|
|
return username[:index]
|
|
}
|
|
}
|
|
return username
|
|
}
|