mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:57:24 +00:00
fix: claim verified domain from usernames (#603)
* fix: return orgDomain validationType * added missing translations for orgDomain activity * claim org domain * show message if domain token was requested * fix tests * fix tests Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
parent
406924bed8
commit
1a00faf132
@ -1,5 +1,6 @@
|
||||
SystemDefaults:
|
||||
DefaultLanguage: 'de'
|
||||
DefaultDomain: $ZITADEL_DEFAULT_DOMAIN
|
||||
ZitadelDocs:
|
||||
Issuer: $ZITADEL_ISSUER
|
||||
DiscoveryEndpoint: '$ZITADEL_ISSUER/.well-known/openid-configuration'
|
||||
|
@ -5,7 +5,10 @@
|
||||
<p class="desc warn">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_VALIDATION_DESC' | translate }}</p>
|
||||
|
||||
|
||||
<div class="btn-container">
|
||||
<p *ngIf="domain.validationType !== OrgDomainValidationType.ORGDOMAINVALIDATIONTYPE_UNSPECIFIED" class="desc">
|
||||
{{'ORG.PAGES.ORGDOMAIN_VERIFICATION_VALIDATION_ONGOING' | translate: domain }}</p>
|
||||
<div *ngIf="domain.validationType !== OrgDomainValidationType.ORGDOMAINVALIDATIONTYPE_UNSPECIFIED"
|
||||
class="btn-container">
|
||||
<button color="primary" type="submit" mat-raised-button
|
||||
(click)="validate()">{{ 'ACTIONS.VERIFY' | translate }}</button>
|
||||
</div>
|
||||
|
@ -119,7 +119,18 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
this.orgService.AddMyOrgDomain(resp).then(domain => {
|
||||
this.domains.push(domain.toObject());
|
||||
const newDomain = domain;
|
||||
|
||||
const newDomainView = new OrgDomainView();
|
||||
newDomainView.setChangeDate(newDomain.getChangeDate());
|
||||
newDomainView.setCreationDate(newDomain.getCreationDate());
|
||||
newDomainView.setDomain(newDomain.getDomain());
|
||||
newDomainView.setOrgId(newDomain.getOrgId());
|
||||
newDomainView.setPrimary(newDomain.getPrimary());
|
||||
newDomainView.setSequence(newDomain.getSequence());
|
||||
newDomainView.setVerified(newDomain.getVerified());
|
||||
|
||||
this.domains.push(newDomainView.toObject());
|
||||
this.toast.showInfo('ORG.TOAST.DOMAINADDED', true);
|
||||
});
|
||||
}
|
||||
|
@ -285,6 +285,7 @@
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_DESC":"Die Tokens werden regelmäßig überprüft, um sicherzustellen, dass sie weiterhin Besitzer der Domain sind.",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_TITLE":"Neues Token anfordern",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_DESC":"Wenn Sie ein neues Token anfordern wollen, klicken Sie auf die gewünschte Methode. Wenn Sie ein vorhandenes Token validieren möchten Klicken Sie auf Validieren.",
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_ONGOING":"Ein Verifikationstoken wurde bereits angefragt. Klicke auf dem Button um dieses zu verifizieren!",
|
||||
"DOWNLOAD_FILE":"Datei download",
|
||||
"SELECTORGTOOLTIP":"Wähle diese Organisation",
|
||||
"PRIMARYDOMAIN":"Primäre Domain",
|
||||
|
@ -285,6 +285,7 @@
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_DESC":"The tokens are checked regularly to ensure you are still owner of the domain.",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_TITLE":"Request new token",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_DESC":"If you want to request a new token, select you preferred method, if you want to validate a persisting token, click on the button above.",
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_ONGOING":"A verification token has already been requested. Click on the button to trigger a verification check!",
|
||||
"DOWNLOAD_FILE":"Download file",
|
||||
"SELECTORGTOOLTIP":"Select this organisation",
|
||||
"PRIMARYDOMAIN":"Primary Domain",
|
||||
|
@ -2,11 +2,13 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
admin_model "github.com/caos/zitadel/internal/admin/model"
|
||||
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/sdk"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
@ -40,7 +42,14 @@ func (repo *OrgRepo) SetUpOrg(ctx context.Context, setUp *admin_model.SetupOrg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, setUp.Org)
|
||||
users := func(ctx context.Context, domain string) ([]*es_models.Aggregate, error) {
|
||||
userIDs, err := repo.View.UserIDsByDomain(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.UserEventstore.PrepareDomainClaimed(ctx, userIDs)
|
||||
}
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, setUp.Org, users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import (
|
||||
|
||||
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/query"
|
||||
org_event "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||
)
|
||||
|
||||
@ -24,12 +26,14 @@ type handler struct {
|
||||
|
||||
type EventstoreRepos struct {
|
||||
UserEvents *usr_event.UserEventstore
|
||||
OrgEvents *org_event.OrgEventstore
|
||||
}
|
||||
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, repos EventstoreRepos) []query.Handler {
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos) []query.Handler {
|
||||
return []query.Handler{
|
||||
&Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}},
|
||||
&IamMember{handler: handler{view, bulkLimit, configs.cycleDuration("IamMember"), errorCount}, userEvents: repos.UserEvents},
|
||||
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}, eventstore: eventstore, orgEvents: repos.OrgEvents},
|
||||
}
|
||||
}
|
||||
|
||||
|
177
internal/admin/repository/eventsourcing/handler/user.go
Normal file
177
internal/admin/repository/eventsourcing/handler/user.go
Normal file
@ -0,0 +1,177 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
handler
|
||||
eventstore eventstore.Eventstore
|
||||
orgEvents *org_events.OrgEventstore
|
||||
}
|
||||
|
||||
const (
|
||||
userTable = "adminapi.users"
|
||||
)
|
||||
|
||||
func (u *User) ViewModel() string {
|
||||
return userTable
|
||||
}
|
||||
|
||||
func (u *User) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := u.view.GetLatestUserSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(es_model.UserAggregate, org_es_model.OrgAggregate).
|
||||
LatestSequenceFilter(sequence.CurrentSequence), nil
|
||||
}
|
||||
|
||||
func (u *User) Reduce(event *models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case es_model.UserAggregate:
|
||||
return u.ProcessUser(event)
|
||||
case org_es_model.OrgAggregate:
|
||||
return u.ProcessOrg(event)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) ProcessUser(event *models.Event) (err error) {
|
||||
user := new(view_model.UserView)
|
||||
switch event.Type {
|
||||
case es_model.UserAdded,
|
||||
es_model.UserRegistered:
|
||||
err = user.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.fillLoginNames(user)
|
||||
case es_model.UserProfileChanged,
|
||||
es_model.UserEmailChanged,
|
||||
es_model.UserEmailVerified,
|
||||
es_model.UserPhoneChanged,
|
||||
es_model.UserPhoneVerified,
|
||||
es_model.UserPhoneRemoved,
|
||||
es_model.UserAddressChanged,
|
||||
es_model.UserDeactivated,
|
||||
es_model.UserReactivated,
|
||||
es_model.UserLocked,
|
||||
es_model.UserUnlocked,
|
||||
es_model.MfaOtpAdded,
|
||||
es_model.MfaOtpVerified,
|
||||
es_model.MfaOtpRemoved:
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
case es_model.DomainClaimed:
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.fillLoginNames(user)
|
||||
case es_model.UserRemoved:
|
||||
err = u.view.DeleteUser(event.AggregateID, event.Sequence)
|
||||
default:
|
||||
return u.view.ProcessedUserSequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.view.PutUser(user, user.Sequence)
|
||||
}
|
||||
|
||||
func (u *User) ProcessOrg(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case org_es_model.OrgDomainVerified,
|
||||
org_es_model.OrgDomainRemoved,
|
||||
org_es_model.OrgIamPolicyAdded,
|
||||
org_es_model.OrgIamPolicyChanged,
|
||||
org_es_model.OrgIamPolicyRemoved:
|
||||
return u.fillLoginNamesOnOrgUsers(event)
|
||||
case org_es_model.OrgDomainPrimarySet:
|
||||
return u.fillPreferredLoginNamesOnOrgUsers(event)
|
||||
default:
|
||||
return u.view.ProcessedUserSequence(event.Sequence)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *User) fillLoginNamesOnOrgUsers(event *models.Event) error {
|
||||
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.ResourceOwner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), event.ResourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
users, err := u.view.UsersByOrgID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range users {
|
||||
user.SetLoginNames(policy, org.Domains)
|
||||
}
|
||||
return u.view.PutUsers(users, event.Sequence)
|
||||
}
|
||||
|
||||
func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error {
|
||||
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(event.ResourceOwner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), event.ResourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !policy.UserLoginMustBeDomain {
|
||||
return nil
|
||||
}
|
||||
users, err := u.view.UsersByOrgID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range users {
|
||||
user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain)
|
||||
}
|
||||
return u.view.PutUsers(users, event.Sequence)
|
||||
}
|
||||
|
||||
func (u *User) fillLoginNames(user *view_model.UserView) (err error) {
|
||||
org, err := u.orgEvents.OrgByID(context.Background(), org_model.NewOrg(user.ResourceOwner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
policy, err := u.orgEvents.GetOrgIamPolicy(context.Background(), user.ResourceOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.SetLoginNames(policy, org.Domains)
|
||||
user.PreferredLoginName = user.GenerateLoginName(org.GetPrimaryDomain().Domain, policy.UserLoginMustBeDomain)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-is8wa", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||
return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip)
|
||||
}
|
@ -86,7 +86,7 @@ func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, r
|
||||
err = setup.StartSetup(systemDefaults, eventstoreRepos).Execute(ctx)
|
||||
logging.Log("SERVE-djs3R").OnError(err).Panic("failed to execute setup")
|
||||
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, handler.EventstoreRepos{UserEvents: user})
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, handler.EventstoreRepos{UserEvents: user, OrgEvents: org})
|
||||
|
||||
return &EsRepository{
|
||||
spooler: spool,
|
||||
|
@ -211,7 +211,7 @@ func (setUp *initializer) org(ctx context.Context, org types.Org) (*org_model.Or
|
||||
Name: org.Name,
|
||||
Domains: []*org_model.OrgDomain{{Domain: org.Domain}},
|
||||
}
|
||||
return setUp.repos.OrgEvents.CreateOrg(ctx, createOrg)
|
||||
return setUp.repos.OrgEvents.CreateOrg(ctx, createOrg, nil)
|
||||
}
|
||||
|
||||
func (setUp *initializer) iamorgpolicy(ctx context.Context, org *org_model.Org) (*org_model.OrgIamPolicy, error) {
|
||||
|
@ -21,7 +21,7 @@ func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sq
|
||||
Eventstore: es,
|
||||
Locker: &locker{dbClient: sql},
|
||||
ConcurrentWorkers: c.ConcurrentWorkers,
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, repos),
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, repos),
|
||||
}
|
||||
spool := spoolerConfig.New()
|
||||
spool.Start()
|
||||
|
83
internal/admin/repository/eventsourcing/view/user.go
Normal file
83
internal/admin/repository/eventsourcing/view/user.go
Normal file
@ -0,0 +1,83 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/view"
|
||||
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
userTable = "adminapi.users"
|
||||
)
|
||||
|
||||
func (v *View) UserByID(userID string) (*model.UserView, error) {
|
||||
return view.UserByID(v.Db, userTable, userID)
|
||||
}
|
||||
|
||||
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
|
||||
return view.SearchUsers(v.Db, userTable, request)
|
||||
}
|
||||
|
||||
func (v *View) GetGlobalUserByEmail(email string) (*model.UserView, error) {
|
||||
return view.GetGlobalUserByEmail(v.Db, userTable, email)
|
||||
}
|
||||
|
||||
func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
|
||||
return view.UsersByOrgID(v.Db, userTable, orgID)
|
||||
}
|
||||
|
||||
func (v *View) UserIDsByDomain(domain string) ([]string, error) {
|
||||
return view.UserIDsByDomain(v.Db, userTable, domain)
|
||||
}
|
||||
|
||||
func (v *View) IsUserUnique(userName, email string) (bool, error) {
|
||||
return view.IsUserUnique(v.Db, userTable, userName, email)
|
||||
}
|
||||
|
||||
func (v *View) UserMfas(userID string) ([]*usr_model.MultiFactor, error) {
|
||||
return view.UserMfas(v.Db, userTable, userID)
|
||||
}
|
||||
|
||||
func (v *View) PutUsers(user []*model.UserView, sequence uint64) error {
|
||||
err := view.PutUsers(v.Db, userTable, user...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedUserSequence(sequence)
|
||||
}
|
||||
|
||||
func (v *View) PutUser(user *model.UserView, sequence uint64) error {
|
||||
err := view.PutUser(v.Db, userTable, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sequence != 0 {
|
||||
return v.ProcessedUserSequence(sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *View) DeleteUser(userID string, eventSequence uint64) error {
|
||||
err := view.DeleteUser(v.Db, userTable, userID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedUserSequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserSequence() (*repository.CurrentSequence, error) {
|
||||
return v.latestSequence(userTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedUserSequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(userTable, eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserFailedEvent(sequence uint64) (*repository.FailedEvent, error) {
|
||||
return v.latestFailedEvent(userTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedUserFailedEvent(failedEvent *repository.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@ -90,6 +90,17 @@ func orgDomainValidationTypeToModel(key management.OrgDomainValidationType) org_
|
||||
}
|
||||
}
|
||||
|
||||
func orgDomainValidationTypeFromModel(key org_model.OrgDomainValidationType) management.OrgDomainValidationType {
|
||||
switch key {
|
||||
case org_model.OrgDomainValidationTypeHTTP:
|
||||
return management.OrgDomainValidationType_ORGDOMAINVALIDATIONTYPE_HTTP
|
||||
case org_model.OrgDomainValidationTypeDNS:
|
||||
return management.OrgDomainValidationType_ORGDOMAINVALIDATIONTYPE_DNS
|
||||
default:
|
||||
return management.OrgDomainValidationType_ORGDOMAINVALIDATIONTYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func primaryOrgDomainToModel(domain *management.PrimaryOrgDomainRequest) *org_model.OrgDomain {
|
||||
return &org_model.OrgDomain{Domain: domain.Domain}
|
||||
}
|
||||
@ -119,12 +130,13 @@ func orgDomainViewFromModel(domain *org_model.OrgDomainView) *management.OrgDoma
|
||||
logging.Log("GRPC-8iSji").OnError(err).Debug("unable to get timestamp from time")
|
||||
|
||||
return &management.OrgDomainView{
|
||||
ChangeDate: changeDate,
|
||||
CreationDate: creationDate,
|
||||
OrgId: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Verified: domain.Verified,
|
||||
Primary: domain.Primary,
|
||||
ChangeDate: changeDate,
|
||||
CreationDate: creationDate,
|
||||
OrgId: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Verified: domain.Verified,
|
||||
Primary: domain.Primary,
|
||||
ValidationType: orgDomainValidationTypeFromModel(domain.ValidationType),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,12 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
auth_model "github.com/caos/zitadel/internal/auth/model"
|
||||
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/sdk"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
@ -57,7 +60,14 @@ func (repo *OrgRepository) RegisterOrg(ctx context.Context, register *auth_model
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, register.Org)
|
||||
users := func(ctx context.Context, domain string) ([]*es_models.Aggregate, error) {
|
||||
userIDs, err := repo.View.UserIDsByDomain(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.UserEventstore.PrepareDomainClaimed(ctx, userIDs)
|
||||
}
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, register.Org, users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -27,12 +27,12 @@ const (
|
||||
userTable = "auth.users"
|
||||
)
|
||||
|
||||
func (p *User) ViewModel() string {
|
||||
func (u *User) ViewModel() string {
|
||||
return userTable
|
||||
}
|
||||
|
||||
func (p *User) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestUserSequence()
|
||||
func (u *User) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := u.view.GetLatestUserSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -52,7 +52,7 @@ func (u *User) Reduce(event *models.Event) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
func (u *User) ProcessUser(event *models.Event) (err error) {
|
||||
user := new(view_model.UserView)
|
||||
switch event.Type {
|
||||
case es_model.UserAdded,
|
||||
@ -61,7 +61,7 @@ func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.fillLoginNames(user)
|
||||
u.fillLoginNames(user)
|
||||
case es_model.UserProfileChanged,
|
||||
es_model.UserEmailChanged,
|
||||
es_model.UserEmailVerified,
|
||||
@ -78,20 +78,30 @@ func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
es_model.MfaOtpRemoved,
|
||||
es_model.MfaInitSkipped,
|
||||
es_model.UserPasswordChanged:
|
||||
user, err = p.view.UserByID(event.AggregateID)
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
case es_model.DomainClaimed:
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.fillLoginNames(user)
|
||||
case es_model.UserRemoved:
|
||||
err = p.view.DeleteUser(event.AggregateID, event.Sequence)
|
||||
err = u.view.DeleteUser(event.AggregateID, event.Sequence)
|
||||
default:
|
||||
return p.view.ProcessedUserSequence(event.Sequence)
|
||||
return u.view.ProcessedUserSequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutUser(user, user.Sequence)
|
||||
return u.view.PutUser(user, user.Sequence)
|
||||
}
|
||||
|
||||
func (u *User) fillLoginNames(user *view_model.UserView) (err error) {
|
||||
@ -172,7 +182,7 @@ func (u *User) fillPreferredLoginNamesOnOrgUsers(event *models.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *User) OnError(event *models.Event, err error) error {
|
||||
func (u *User) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-is8wa", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||
return spooler.HandleError(event, err, p.view.GetLatestUserFailedEvent, p.view.ProcessedUserFailedEvent, p.view.ProcessedUserSequence, p.errorCountUntilSkip)
|
||||
return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip)
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ func (v *View) UserByLoginName(loginName string) (*model.UserView, error) {
|
||||
func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
|
||||
return view.UsersByOrgID(v.Db, userTable, orgID)
|
||||
}
|
||||
|
||||
func (v *View) UserIDsByDomain(domain string) ([]string, error) {
|
||||
return view.UserIDsByDomain(v.Db, userTable, domain)
|
||||
}
|
||||
|
||||
func (v *View) SearchUsers(request *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
|
||||
return view.SearchUsers(v.Db, userTable, request)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
|
||||
type SystemDefaults struct {
|
||||
DefaultLanguage language.Tag
|
||||
DefaultDomain string
|
||||
ZitadelDocs ZitadelDocs
|
||||
SecretGenerators SecretGenerators
|
||||
UserVerificationKey *crypto.KeyConfig
|
||||
|
@ -2,11 +2,12 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"github.com/caos/logging"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/sdk"
|
||||
mgmt_view "github.com/caos/zitadel/internal/management/repository/eventsourcing/view"
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
@ -15,8 +16,6 @@ import (
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/view/model"
|
||||
usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||
|
||||
|
||||
)
|
||||
|
||||
const (
|
||||
@ -48,7 +47,7 @@ func (repo *OrgRepository) OrgByDomainGlobal(ctx context.Context, domain string)
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) CreateOrg(ctx context.Context, name string) (*org_model.Org, error) {
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, &org_model.Org{Name: name})
|
||||
org, aggregates, err := repo.OrgEventstore.PrepareCreateOrg(ctx, &org_model.Org{Name: name}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -118,7 +117,14 @@ func (repo *OrgRepository) GenerateMyOrgDomainValidation(ctx context.Context, do
|
||||
|
||||
func (repo *OrgRepository) ValidateMyOrgDomain(ctx context.Context, domain *org_model.OrgDomain) error {
|
||||
domain.AggregateID = authz.GetCtxData(ctx).OrgID
|
||||
return repo.OrgEventstore.ValidateOrgDomain(ctx, domain)
|
||||
users := func(ctx context.Context, domain string) ([]*es_models.Aggregate, error) {
|
||||
userIDs, err := repo.View.UserIDsByDomain(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.UserEvents.PrepareDomainClaimed(ctx, userIDs)
|
||||
}
|
||||
return repo.OrgEventstore.ValidateOrgDomain(ctx, domain, users)
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) SetMyPrimaryOrgDomain(ctx context.Context, domain *org_model.OrgDomain) error {
|
||||
|
@ -45,7 +45,8 @@ func (d *OrgDomain) processOrgDomain(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case model.OrgDomainAdded:
|
||||
err = domain.AppendEvent(event)
|
||||
case model.OrgDomainVerified:
|
||||
case model.OrgDomainVerified,
|
||||
model.OrgDomainVerificationAdded:
|
||||
err = domain.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -26,12 +26,12 @@ const (
|
||||
userTable = "management.users"
|
||||
)
|
||||
|
||||
func (p *User) ViewModel() string {
|
||||
func (u *User) ViewModel() string {
|
||||
return userTable
|
||||
}
|
||||
|
||||
func (p *User) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestUserSequence()
|
||||
func (u *User) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := u.view.GetLatestUserSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -51,7 +51,7 @@ func (u *User) Reduce(event *models.Event) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
func (u *User) ProcessUser(event *models.Event) (err error) {
|
||||
user := new(view_model.UserView)
|
||||
switch event.Type {
|
||||
case es_model.UserAdded,
|
||||
@ -60,7 +60,7 @@ func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.fillLoginNames(user)
|
||||
err = u.fillLoginNames(user)
|
||||
case es_model.UserProfileChanged,
|
||||
es_model.UserEmailChanged,
|
||||
es_model.UserEmailVerified,
|
||||
@ -75,20 +75,30 @@ func (p *User) ProcessUser(event *models.Event) (err error) {
|
||||
es_model.MfaOtpAdded,
|
||||
es_model.MfaOtpVerified,
|
||||
es_model.MfaOtpRemoved:
|
||||
user, err = p.view.UserByID(event.AggregateID)
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
case es_model.DomainClaimed:
|
||||
user, err = u.view.UserByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = user.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.fillLoginNames(user)
|
||||
case es_model.UserRemoved:
|
||||
err = p.view.DeleteUser(event.AggregateID, event.Sequence)
|
||||
err = u.view.DeleteUser(event.AggregateID, event.Sequence)
|
||||
default:
|
||||
return p.view.ProcessedUserSequence(event.Sequence)
|
||||
return u.view.ProcessedUserSequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutUser(user, user.Sequence)
|
||||
return u.view.PutUser(user, user.Sequence)
|
||||
}
|
||||
|
||||
func (u *User) ProcessOrg(event *models.Event) (err error) {
|
||||
@ -161,7 +171,7 @@ func (u *User) fillLoginNames(user *view_model.UserView) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *User) OnError(event *models.Event, err error) error {
|
||||
func (u *User) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-is8wa", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||
return spooler.HandleError(event, err, p.view.GetLatestUserFailedEvent, p.view.ProcessedUserFailedEvent, p.view.ProcessedUserSequence, p.errorCountUntilSkip)
|
||||
return spooler.HandleError(event, err, u.view.GetLatestUserFailedEvent, u.view.ProcessedUserFailedEvent, u.view.ProcessedUserSequence, u.errorCountUntilSkip)
|
||||
}
|
||||
|
@ -27,6 +27,10 @@ func (v *View) UsersByOrgID(orgID string) ([]*model.UserView, error) {
|
||||
return view.UsersByOrgID(v.Db, userTable, orgID)
|
||||
}
|
||||
|
||||
func (v *View) UserIDsByDomain(domain string) ([]string, error) {
|
||||
return view.UserIDsByDomain(v.Db, userTable, domain)
|
||||
}
|
||||
|
||||
func (v *View) IsUserUnique(userName, email string) (bool, error) {
|
||||
return view.IsUserUnique(v.Db, userTable, userName, email)
|
||||
}
|
||||
|
@ -14,4 +14,6 @@ const (
|
||||
SearchMethodLessThan
|
||||
SearchMethodIsOneOf
|
||||
SearchMethodListContains
|
||||
SearchMethodEndsWith
|
||||
SearchMethodEndsWithIgnoreCase
|
||||
)
|
||||
|
@ -1,17 +1,19 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
)
|
||||
|
||||
type OrgDomainView struct {
|
||||
OrgID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Domain string
|
||||
Primary bool
|
||||
Verified bool
|
||||
OrgID string
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
Domain string
|
||||
Primary bool
|
||||
Verified bool
|
||||
ValidationType OrgDomainValidationType
|
||||
}
|
||||
|
||||
type OrgDomainSearchRequest struct {
|
||||
|
@ -53,7 +53,7 @@ func StartOrg(conf OrgConfig, defaults systemdefaults.SystemDefaults) *OrgEvents
|
||||
}
|
||||
}
|
||||
|
||||
func (es *OrgEventstore) PrepareCreateOrg(ctx context.Context, orgModel *org_model.Org) (*model.Org, []*es_models.Aggregate, error) {
|
||||
func (es *OrgEventstore) PrepareCreateOrg(ctx context.Context, orgModel *org_model.Org, users func(context.Context, string) ([]*es_models.Aggregate, error)) (*model.Org, []*es_models.Aggregate, error) {
|
||||
if orgModel == nil || !orgModel.IsValid() {
|
||||
return nil, nil, errors.ThrowInvalidArgument(nil, "EVENT-OeLSk", "Errors.Org.Invalid")
|
||||
}
|
||||
@ -66,13 +66,13 @@ func (es *OrgEventstore) PrepareCreateOrg(ctx context.Context, orgModel *org_mod
|
||||
orgModel.AggregateID = id
|
||||
org := model.OrgFromModel(orgModel)
|
||||
|
||||
aggregates, err := orgCreatedAggregates(ctx, es.AggregateCreator(), org)
|
||||
aggregates, err := orgCreatedAggregates(ctx, es.AggregateCreator(), org, users)
|
||||
|
||||
return org, aggregates, err
|
||||
}
|
||||
|
||||
func (es *OrgEventstore) CreateOrg(ctx context.Context, orgModel *org_model.Org) (*org_model.Org, error) {
|
||||
org, aggregates, err := es.PrepareCreateOrg(ctx, orgModel)
|
||||
func (es *OrgEventstore) CreateOrg(ctx context.Context, orgModel *org_model.Org, users func(context.Context, string) ([]*es_models.Aggregate, error)) (*org_model.Org, error) {
|
||||
org, aggregates, err := es.PrepareCreateOrg(ctx, orgModel, users)
|
||||
err = es_sdk.PushAggregates(ctx, es.PushAggregates, org.AppendEvents, aggregates...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -222,7 +222,7 @@ func (es *OrgEventstore) GenerateOrgDomainValidation(ctx context.Context, domain
|
||||
return token, url, err
|
||||
}
|
||||
|
||||
func (es *OrgEventstore) ValidateOrgDomain(ctx context.Context, domain *org_model.OrgDomain) error {
|
||||
func (es *OrgEventstore) ValidateOrgDomain(ctx context.Context, domain *org_model.OrgDomain, users func(context.Context, string) ([]*es_models.Aggregate, error)) error {
|
||||
if domain == nil || !domain.IsValid() {
|
||||
return errors.ThrowPreconditionFailed(nil, "EVENT-R24hb", "Errors.Org.InvalidDomain")
|
||||
}
|
||||
@ -249,7 +249,7 @@ func (es *OrgEventstore) ValidateOrgDomain(ctx context.Context, domain *org_mode
|
||||
checkType, _ := d.ValidationType.CheckType()
|
||||
err = es.verificationValidator(d.Domain, validationCode, validationCode, checkType)
|
||||
if err == nil {
|
||||
orgAggregates, err := OrgDomainVerifiedAggregate(ctx, es.Eventstore.AggregateCreator(), repoOrg, repoDomain)
|
||||
orgAggregates, err := OrgDomainVerifiedAggregate(ctx, es.Eventstore.AggregateCreator(), repoOrg, repoDomain, users)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -578,6 +578,7 @@ func TestOrgEventstore_ValidateOrgDomain(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
domain *org_model.OrgDomain
|
||||
users func(ctx context.Context, domain string) ([]*es_models.Aggregate, error)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -682,6 +683,26 @@ func TestOrgEventstore_ValidateOrgDomain(t *testing.T) {
|
||||
isErr: errors.IsInternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "(user) aggregate fails",
|
||||
fields: fields{Eventstore: newTestEventstore(t).
|
||||
expectFilterEvents([]*es_models.Event{orgCreatedEvent(), orgDomainAddedEvent(), orgDomainVerificationAddedEvent("token")}, nil).
|
||||
expectDecrypt().
|
||||
expectVerification(true).
|
||||
expectAggregateCreator().
|
||||
expectPushEvents(0, errors.ThrowInternal(nil, "EVENT-S8WzW", "test")),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("org", "user"),
|
||||
domain: &org_model.OrgDomain{ObjectRoot: es_models.ObjectRoot{AggregateID: "hodor-org"}, Domain: "hodor.org", ValidationType: org_model.OrgDomainValidationTypeHTTP},
|
||||
users: func(ctx context.Context, domain string) ([]*es_models.Aggregate, error) {
|
||||
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "push failed",
|
||||
fields: fields{Eventstore: newTestEventstore(t).
|
||||
@ -719,7 +740,7 @@ func TestOrgEventstore_ValidateOrgDomain(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.fields.Eventstore.ValidateOrgDomain(tt.args.ctx, tt.args.domain)
|
||||
err := tt.fields.Eventstore.ValidateOrgDomain(tt.args.ctx, tt.args.domain, tt.args.users)
|
||||
if tt.res.isErr == nil && err != nil {
|
||||
t.Errorf("no error expected got:%T %v", err, err)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package eventsourcing
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
@ -42,7 +43,7 @@ func OrgAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, i
|
||||
return aggCreator.NewAggregate(ctx, id, model.OrgAggregate, model.OrgVersion, sequence)
|
||||
}
|
||||
|
||||
func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, org *model.Org) (_ []*es_models.Aggregate, err error) {
|
||||
func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, org *model.Org, users func(context.Context, string) ([]*es_models.Aggregate, error)) (_ []*es_models.Aggregate, err error) {
|
||||
if org == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie7", "Errors.Internal")
|
||||
}
|
||||
@ -56,7 +57,7 @@ func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCr
|
||||
return nil, err
|
||||
}
|
||||
aggregates := make([]*es_models.Aggregate, 0)
|
||||
aggregates, err = addDomainAggregateAndEvents(ctx, aggCreator, agg, aggregates, org)
|
||||
aggregates, err = addDomainAggregateAndEvents(ctx, aggCreator, agg, aggregates, org, users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -68,22 +69,18 @@ func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCr
|
||||
return append(aggregates, agg), nil
|
||||
}
|
||||
|
||||
func addDomainAggregateAndEvents(ctx context.Context, aggCreator *es_models.AggregateCreator, orgAggregate *es_models.Aggregate, aggregates []*es_models.Aggregate, org *model.Org) ([]*es_models.Aggregate, error) {
|
||||
func addDomainAggregateAndEvents(ctx context.Context, aggCreator *es_models.AggregateCreator, orgAggregate *es_models.Aggregate, aggregates []*es_models.Aggregate, org *model.Org, users func(context.Context, string) ([]*es_models.Aggregate, error)) ([]*es_models.Aggregate, error) {
|
||||
for _, domain := range org.Domains {
|
||||
orgAggregate, err := orgAggregate.AppendEvent(model.OrgDomainAdded, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domain.Verified {
|
||||
domainAggregate, err := reservedUniqueDomainAggregate(ctx, aggCreator, org.AggregateID, domain.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates = append(aggregates, domainAggregate)
|
||||
orgAggregate, err = orgAggregate.AppendEvent(model.OrgDomainVerified, domain)
|
||||
domainAggregates, err := OrgDomainVerifiedAggregate(ctx, aggCreator, org, domain, users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates = append(aggregates, domainAggregates...)
|
||||
}
|
||||
if domain.Primary {
|
||||
orgAggregate, err = orgAggregate.AppendEvent(model.OrgDomainPrimarySet, domain)
|
||||
@ -182,7 +179,6 @@ func reservedUniqueDomainAggregate(ctx context.Context, aggCreator *es_models.Ag
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aggregate.SetPrecondition(OrgDomainUniqueQuery(domain), isEventValidation(aggregate, model.OrgDomainReserved)), nil
|
||||
}
|
||||
|
||||
@ -273,7 +269,7 @@ func OrgDomainValidationFailedAggregate(aggCreator *es_models.AggregateCreator,
|
||||
}
|
||||
}
|
||||
|
||||
func OrgDomainVerifiedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain) ([]*es_models.Aggregate, error) {
|
||||
func OrgDomainVerifiedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain, users func(context.Context, string) ([]*es_models.Aggregate, error)) ([]*es_models.Aggregate, error) {
|
||||
if domain == nil {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-DHs7s", "Errors.Internal")
|
||||
}
|
||||
@ -291,6 +287,13 @@ func OrgDomainVerifiedAggregate(ctx context.Context, aggCreator *es_models.Aggre
|
||||
return nil, err
|
||||
}
|
||||
aggregates = append(aggregates, domainAgregate)
|
||||
if users != nil {
|
||||
userAggregates, err := users(ctx, domain.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "EVENT-HBwsw", "Errors.Internal")
|
||||
}
|
||||
aggregates = append(aggregates, userAggregates...)
|
||||
}
|
||||
return append(aggregates, agg), nil
|
||||
}
|
||||
|
||||
|
@ -466,6 +466,7 @@ func TestOrgCreatedAggregates(t *testing.T) {
|
||||
ctx context.Context
|
||||
aggCreator *es_models.AggregateCreator
|
||||
org *model.Org
|
||||
users func(ctx context.Context, domain string) ([]*es_models.Aggregate, error)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -523,6 +524,30 @@ func TestOrgCreatedAggregates(t *testing.T) {
|
||||
isErr: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org with domain users aggregate error",
|
||||
args: args{
|
||||
ctx: authz.NewMockContext("org", "user"),
|
||||
aggCreator: es_models.NewAggregateCreator("test"),
|
||||
org: &model.Org{
|
||||
ObjectRoot: es_models.ObjectRoot{
|
||||
AggregateID: "sdaf",
|
||||
Sequence: 5,
|
||||
},
|
||||
Name: "caos",
|
||||
Domains: []*model.OrgDomain{{
|
||||
Domain: "caos.ch",
|
||||
Verified: true,
|
||||
}},
|
||||
},
|
||||
users: func(ctx context.Context, domain string) ([]*es_models.Aggregate, error) {
|
||||
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
isErr: errors.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no name error",
|
||||
args: args{
|
||||
@ -543,7 +568,7 @@ func TestOrgCreatedAggregates(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := orgCreatedAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.org)
|
||||
got, err := orgCreatedAggregates(tt.args.ctx, tt.args.aggCreator, tt.args.org, tt.args.users)
|
||||
if tt.res.isErr == nil && err != nil {
|
||||
t.Errorf("no error expected got %T: %v", err, err)
|
||||
}
|
||||
@ -676,7 +701,7 @@ func TestOrgDomainVerifiedAggregates(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := OrgDomainVerifiedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.org, tt.args.domain)
|
||||
got, err := OrgDomainVerifiedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.org, tt.args.domain, nil)
|
||||
if tt.res.isErr == nil && err != nil {
|
||||
t.Errorf("no error expected got %T: %v", err, err)
|
||||
}
|
||||
|
@ -2,12 +2,14 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/org/model"
|
||||
es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -18,11 +20,12 @@ const (
|
||||
)
|
||||
|
||||
type OrgDomainView struct {
|
||||
Domain string `json:"domain" gorm:"column:domain;primary_key"`
|
||||
OrgID string `json:"-" gorm:"column:org_id;primary_key"`
|
||||
Verified bool `json:"-" gorm:"column:verified"`
|
||||
Primary bool `json:"-" gorm:"column:primary_domain"`
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
Domain string `json:"domain" gorm:"column:domain;primary_key"`
|
||||
OrgID string `json:"-" gorm:"column:org_id;primary_key"`
|
||||
Verified bool `json:"-" gorm:"column:verified"`
|
||||
Primary bool `json:"-" gorm:"column:primary_domain"`
|
||||
ValidationType int32 `json:"validationType" gorm:"column:validation_type"`
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
|
||||
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
|
||||
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
|
||||
@ -30,23 +33,25 @@ type OrgDomainView struct {
|
||||
|
||||
func OrgDomainViewFromModel(domain *model.OrgDomainView) *OrgDomainView {
|
||||
return &OrgDomainView{
|
||||
OrgID: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Primary: domain.Primary,
|
||||
Verified: domain.Verified,
|
||||
CreationDate: domain.CreationDate,
|
||||
ChangeDate: domain.ChangeDate,
|
||||
OrgID: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Primary: domain.Primary,
|
||||
Verified: domain.Verified,
|
||||
ValidationType: int32(domain.ValidationType),
|
||||
CreationDate: domain.CreationDate,
|
||||
ChangeDate: domain.ChangeDate,
|
||||
}
|
||||
}
|
||||
|
||||
func OrgDomainToModel(domain *OrgDomainView) *model.OrgDomainView {
|
||||
return &model.OrgDomainView{
|
||||
OrgID: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Primary: domain.Primary,
|
||||
Verified: domain.Verified,
|
||||
CreationDate: domain.CreationDate,
|
||||
ChangeDate: domain.ChangeDate,
|
||||
OrgID: domain.OrgID,
|
||||
Domain: domain.Domain,
|
||||
Primary: domain.Primary,
|
||||
Verified: domain.Verified,
|
||||
ValidationType: model.OrgDomainValidationType(domain.ValidationType),
|
||||
CreationDate: domain.CreationDate,
|
||||
ChangeDate: domain.ChangeDate,
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,6 +71,8 @@ func (d *OrgDomainView) AppendEvent(event *models.Event) (err error) {
|
||||
d.setRootData(event)
|
||||
d.CreationDate = event.CreationDate
|
||||
err = d.SetData(event)
|
||||
case es_model.OrgDomainVerificationAdded:
|
||||
err = d.SetData(event)
|
||||
case es_model.OrgDomainVerified:
|
||||
d.Verified = true
|
||||
case es_model.OrgDomainPrimarySet:
|
||||
|
@ -203,6 +203,9 @@ EventTypes:
|
||||
removed: Organisation entfernt
|
||||
domain:
|
||||
added: Domäne hinzugefügt
|
||||
verification:
|
||||
added: Domänenverifizierung hinzugefügt
|
||||
failed: Domänenverifizierung fehlgeschlagen
|
||||
verified: Domäne verifiziert
|
||||
removed: Domäne entfernt
|
||||
primary:
|
||||
|
@ -203,6 +203,9 @@ EventTypes:
|
||||
removed: Organization removed
|
||||
domain:
|
||||
added: Domain added
|
||||
verification:
|
||||
added: Domain verification added
|
||||
failed: Domain verification failed
|
||||
verified: Domain verified
|
||||
removed: Domain removed
|
||||
primary:
|
||||
|
@ -3,13 +3,15 @@ package eventsourcing
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
policy_model "github.com/caos/zitadel/internal/policy/model"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
@ -30,6 +32,7 @@ type UserEventstore struct {
|
||||
es_int.Eventstore
|
||||
userCache *UserCache
|
||||
idGenerator id.Generator
|
||||
defaultDomain string
|
||||
PasswordAlg crypto.HashAlgorithm
|
||||
InitializeUserCode crypto.Generator
|
||||
EmailVerificationCode crypto.Generator
|
||||
@ -65,6 +68,7 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto
|
||||
Eventstore: conf.Eventstore,
|
||||
userCache: userCache,
|
||||
idGenerator: id.SonyFlakeGenerator,
|
||||
defaultDomain: systemDefaults.DefaultDomain,
|
||||
InitializeUserCode: initCodeGen,
|
||||
EmailVerificationCode: emailVerificationCode,
|
||||
PhoneVerificationCode: phoneVerificationCode,
|
||||
@ -1084,3 +1088,32 @@ func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs [
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PrepareDomainClaimed(ctx context.Context, userIDs []string) ([]*es_models.Aggregate, error) {
|
||||
aggregates := make([]*es_models.Aggregate, 0)
|
||||
for _, userID := range userIDs {
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
name, err := es.generateTemporaryLoginName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg, err := DomainClaimedAggregate(ctx, es.AggregateCreator(), repoUser, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates = append(aggregates, userAgg...)
|
||||
}
|
||||
return aggregates, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) generateTemporaryLoginName() (string, error) {
|
||||
id, err := es.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s@temporary.%s", id, es.defaultDomain), nil
|
||||
}
|
||||
|
@ -55,4 +55,6 @@ const (
|
||||
MfaInitSkipped models.EventType = "user.mfa.init.skipped"
|
||||
|
||||
SignedOut models.EventType = "user.signed.out"
|
||||
|
||||
DomainClaimed models.EventType = "user.domain.claimed"
|
||||
)
|
||||
|
@ -133,7 +133,8 @@ func (u *User) AppendEvent(event *es_models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case UserAdded,
|
||||
UserRegistered,
|
||||
UserProfileChanged:
|
||||
UserProfileChanged,
|
||||
DomainClaimed:
|
||||
u.setData(event)
|
||||
case UserDeactivated:
|
||||
u.appendDeactivatedEvent()
|
||||
|
@ -190,6 +190,22 @@ func reservedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.
|
||||
return aggregate.SetPrecondition(UserUserNameUniqueQuery(uniqueUserName), isEventValidation(aggregate, model.UserUserNameReserved)), nil
|
||||
}
|
||||
|
||||
func releasedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, username string) (aggregate *es_models.Aggregate, err error) {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, username, model.UserUserNameAggregate, model.UserVersion, 0)
|
||||
if resourceOwner != "" {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, username, model.UserUserNameAggregate, model.UserVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregate, err = aggregate.AppendEvent(model.UserUserNameReleased, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aggregate.SetPrecondition(UserUserNameUniqueQuery(username), isEventValidation(aggregate, model.UserUserNameReleased)), nil
|
||||
}
|
||||
|
||||
func reservedUniqueEmailAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, email string) (aggregate *es_models.Aggregate, err error) {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0)
|
||||
if resourceOwner != "" {
|
||||
@ -656,6 +672,30 @@ func SignOutAggregates(aggCreator *es_models.AggregateCreator, existingUsers []*
|
||||
}
|
||||
}
|
||||
|
||||
func DomainClaimedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existingUser *model.User, tempName string) ([]*es_models.Aggregate, error) {
|
||||
aggregates := make([]*es_models.Aggregate, 3)
|
||||
userAggregate, err := UserAggregateOverwriteContext(ctx, aggCreator, existingUser, existingUser.ResourceOwner, existingUser.AggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAggregate, err = userAggregate.AppendEvent(model.DomainClaimed, map[string]interface{}{"userName": tempName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[0] = userAggregate
|
||||
releasedUniqueAggregate, err := releasedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, existingUser.UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[1] = releasedUniqueAggregate
|
||||
reservedUniqueAggregate, err := reservedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, tempName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[2] = reservedUniqueAggregate
|
||||
return aggregates, nil
|
||||
}
|
||||
|
||||
func isEventValidation(aggregate *es_models.Aggregate, eventType es_models.EventType) func(...*es_models.Event) error {
|
||||
return func(events ...*es_models.Event) error {
|
||||
if len(events) == 0 {
|
||||
@ -678,6 +718,7 @@ func addUserNameValidation(userName string) func(...*es_models.Event) error {
|
||||
case org_es_model.OrgDomainAdded:
|
||||
domain := new(org_es_model.OrgDomain)
|
||||
domain.SetData(event)
|
||||
domains = append(domains, domain)
|
||||
case org_es_model.OrgDomainVerified:
|
||||
domain := new(org_es_model.OrgDomain)
|
||||
domain.SetData(event)
|
||||
@ -694,6 +735,7 @@ func addUserNameValidation(userName string) func(...*es_models.Event) error {
|
||||
domains[i] = domains[len(domains)-1]
|
||||
domains[len(domains)-1] = nil
|
||||
domains = domains[:len(domains)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,7 +181,8 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
|
||||
case es_model.UserPasswordChanged:
|
||||
err = u.setPasswordData(event)
|
||||
case es_model.UserProfileChanged,
|
||||
es_model.UserAddressChanged:
|
||||
es_model.UserAddressChanged,
|
||||
es_model.DomainClaimed:
|
||||
err = u.setData(event)
|
||||
case es_model.UserEmailChanged:
|
||||
u.IsEmailVerified = false
|
||||
|
@ -2,6 +2,7 @@ package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
@ -59,6 +60,20 @@ func UsersByOrgID(db *gorm.DB, table, orgID string) ([]*model.UserView, error) {
|
||||
return users, err
|
||||
}
|
||||
|
||||
func UserIDsByDomain(db *gorm.DB, table, domain string) ([]string, error) {
|
||||
users := make([]string, 0)
|
||||
orgIDQuery := &usr_model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyUserName,
|
||||
Method: global_model.SearchMethodEndsWithIgnoreCase,
|
||||
Value: "%" + domain,
|
||||
}
|
||||
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{
|
||||
Queries: []*usr_model.UserSearchQuery{orgIDQuery},
|
||||
})
|
||||
_, err := query(db, &users)
|
||||
return users, err
|
||||
}
|
||||
|
||||
func SearchUsers(db *gorm.DB, table string, req *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
|
||||
users := make([]*model.UserView, 0)
|
||||
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||
|
@ -85,6 +85,18 @@ func SetQuery(query *gorm.DB, key ColumnKey, value interface{}, method model.Sea
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-eidus", "Starts with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", valueText+"%")
|
||||
case model.SearchMethodEndsWith:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-Hswd3", "Ends with only possible for strings")
|
||||
}
|
||||
query = query.Where(column+" LIKE ?", "%"+valueText)
|
||||
case model.SearchMethodEndsWithIgnoreCase:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "VIEW-dAG31", "Ends with ignore case only possible for strings")
|
||||
}
|
||||
query = query.Where("LOWER("+column+") LIKE LOWER(?)", "%"+valueText)
|
||||
case model.SearchMethodContains:
|
||||
valueText, ok := value.(string)
|
||||
if !ok {
|
||||
|
44
migrations/cockroach/V1.5__orgdomain_validationtype.sql
Normal file
44
migrations/cockroach/V1.5__orgdomain_validationtype.sql
Normal file
@ -0,0 +1,44 @@
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE management.org_domains ADD COLUMN validation_type SMALLINT;
|
||||
|
||||
CREATE TABLE adminapi.users (
|
||||
id TEXT,
|
||||
|
||||
creation_date TIMESTAMPTZ,
|
||||
change_date TIMESTAMPTZ,
|
||||
|
||||
resource_owner TEXT,
|
||||
user_state SMALLINT,
|
||||
last_login TIMESTAMPTZ,
|
||||
password_change TIMESTAMPTZ,
|
||||
user_name TEXT,
|
||||
login_names TEXT ARRAY,
|
||||
preferred_login_name TEXT,
|
||||
first_name TEXT,
|
||||
last_name TEXT,
|
||||
nick_Name TEXT,
|
||||
display_name TEXT,
|
||||
preferred_language TEXT,
|
||||
gender SMALLINT,
|
||||
email TEXT,
|
||||
is_email_verified BOOLEAN,
|
||||
phone TEXT,
|
||||
is_phone_verified BOOLEAN,
|
||||
country TEXT,
|
||||
locality TEXT,
|
||||
postal_code TEXT,
|
||||
region TEXT,
|
||||
street_address TEXT,
|
||||
otp_state SMALLINT,
|
||||
sequence BIGINT,
|
||||
password_set BOOLEAN,
|
||||
password_change_required BOOLEAN,
|
||||
mfa_max_set_up SMALLINT,
|
||||
mfa_init_skipped TIMESTAMPTZ,
|
||||
init_required BOOLEAN,
|
||||
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
COMMIT;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user