mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-02 12:50:50 +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}
|
||||
}
|
||||
@ -125,6 +136,7 @@ func orgDomainViewFromModel(domain *org_model.OrgDomainView) *management.OrgDoma
|
||||
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,8 +1,9 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/model"
|
||||
)
|
||||
|
||||
type OrgDomainView struct {
|
||||
@ -12,6 +13,7 @@ type OrgDomainView struct {
|
||||
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 (
|
||||
@ -22,6 +24,7 @@ type OrgDomainView struct {
|
||||
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"`
|
||||
@ -34,6 +37,7 @@ func OrgDomainViewFromModel(domain *model.OrgDomainView) *OrgDomainView {
|
||||
Domain: domain.Domain,
|
||||
Primary: domain.Primary,
|
||||
Verified: domain.Verified,
|
||||
ValidationType: int32(domain.ValidationType),
|
||||
CreationDate: domain.CreationDate,
|
||||
ChangeDate: domain.ChangeDate,
|
||||
}
|
||||
@ -45,6 +49,7 @@ func OrgDomainToModel(domain *OrgDomainView) *model.OrgDomainView {
|
||||
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
@ -1264,7 +1264,7 @@ message Iam {
|
||||
message ChangeRequest {
|
||||
string id = 1;
|
||||
string sec_id = 2;
|
||||
uint64 limit= 3;
|
||||
uint64 limit = 3;
|
||||
uint64 sequence_offset = 4;
|
||||
bool asc = 5;
|
||||
}
|
||||
@ -1360,7 +1360,7 @@ enum UserState {
|
||||
USERSTATE_DELETED = 3;
|
||||
USERSTATE_LOCKED = 4;
|
||||
USERSTATE_SUSPEND = 5;
|
||||
USERSTATE_INITIAL= 6;
|
||||
USERSTATE_INITIAL = 6;
|
||||
}
|
||||
|
||||
enum Gender {
|
||||
@ -1781,6 +1781,7 @@ message OrgDomainView {
|
||||
bool verified = 5;
|
||||
bool primary = 6;
|
||||
uint64 sequence = 7;
|
||||
OrgDomainValidationType validation_type = 8;
|
||||
}
|
||||
|
||||
message AddOrgDomainRequest {
|
||||
|
Loading…
x
Reference in New Issue
Block a user