fix: import user, hide login name suffix (#1474)

* fix: import user, and label policy command side

* feat: Import user and hide loginname suffix (#1464)

* fix: import user

* fix: label policy

* fix: label policy

* fix: label policy

* fix: migrations

* fix: migrations

* fix: migrations

* fix: label policy

* loginSuffix in login ui

* suffix

* fix cursor on disabled user selection

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

(cherry picked from commit 03ddb8fc38)

* feat: Import user and hide loginname suffix (#1464)

* fix: import user

* fix: label policy

* fix: label policy

* fix: label policy

* fix: migrations

* fix: migrations

* fix: migrations

* fix: label policy

* loginSuffix in login ui

* suffix

* fix cursor on disabled user selection

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

(cherry picked from commit 03ddb8fc38)

* feat: Import user and hide loginname suffix (#1464)

* fix: import user

* fix: label policy

* fix: label policy

* fix: label policy

* fix: migrations

* fix: migrations

* fix: migrations

* fix: label policy

* loginSuffix in login ui

* suffix

* fix cursor on disabled user selection

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

(cherry picked from commit 03ddb8fc38)

* fix: label policy events

* loginname placeholder

* fix: tests

* fix: tests

* Update internal/command/iam_policy_label_model.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi 2021-03-25 14:41:07 +01:00 committed by GitHub
parent d7255130a4
commit 4d10f3e715
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1444 additions and 309 deletions

View File

@ -7,7 +7,8 @@ import (
func updateLabelPolicyToDomain(policy *admin_pb.UpdateLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}

View File

@ -0,0 +1,64 @@
package management
import (
"context"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object"
policy_grpc "github.com/caos/zitadel/internal/api/grpc/policy"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func (s *Server) GetLabelPolicy(ctx context.Context, req *mgmt_pb.GetLabelPolicyRequest) (*mgmt_pb.GetLabelPolicyResponse, error) {
policy, err := s.org.GetLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) GetDefaultLabelPolicy(ctx context.Context, req *mgmt_pb.GetDefaultLabelPolicyRequest) (*mgmt_pb.GetDefaultLabelPolicyResponse, error) {
policy, err := s.org.GetDefaultLabelPolicy(ctx)
if err != nil {
return nil, err
}
return &mgmt_pb.GetDefaultLabelPolicyResponse{Policy: policy_grpc.ModelLabelPolicyToPb(policy)}, nil
}
func (s *Server) AddCustomLabelPolicy(ctx context.Context, req *mgmt_pb.AddCustomLabelPolicyRequest) (*mgmt_pb.AddCustomLabelPolicyResponse, error) {
policy, err := s.command.AddLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID, addLabelPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddCustomLabelPolicyResponse{
Details: object.AddToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) UpdateCustomLabelPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomLabelPolicyRequest) (*mgmt_pb.UpdateCustomLabelPolicyResponse, error) {
policy, err := s.command.ChangeLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID, updateLabelPolicyToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateCustomLabelPolicyResponse{
Details: object.ChangeToDetailsPb(
policy.Sequence,
policy.ChangeDate,
policy.ResourceOwner,
),
}, nil
}
func (s *Server) ResetLabelPolicyToDefault(ctx context.Context, req *mgmt_pb.ResetLabelPolicyToDefaultRequest) (*mgmt_pb.ResetLabelPolicyToDefaultResponse, error) {
objectDetails, err := s.command.RemoveLabelPolicy(ctx, authz.GetCtxData(ctx).OrgID)
if err != nil {
return nil, err
}
return &mgmt_pb.ResetLabelPolicyToDefaultResponse{
Details: object.DomainToChangeDetailsPb(objectDetails),
}, nil
}

View File

@ -0,0 +1,22 @@
package management
import (
"github.com/caos/zitadel/internal/domain"
mgmt_pb "github.com/caos/zitadel/pkg/grpc/management"
)
func addLabelPolicyToDomain(p *mgmt_pb.AddCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
HideLoginNameSuffix: p.HideLoginNameSuffix,
}
}
func updateLabelPolicyToDomain(p *mgmt_pb.UpdateCustomLabelPolicyRequest) *domain.LabelPolicy {
return &domain.LabelPolicy{
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
HideLoginNameSuffix: p.HideLoginNameSuffix,
}
}

View File

@ -87,6 +87,21 @@ func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequ
}, nil
}
func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) {
human, err := s.command.ImportHuman(ctx, authz.GetCtxData(ctx).OrgID, ImportHumanUserRequestToDomain(req))
if err != nil {
return nil, err
}
return &mgmt_pb.ImportHumanUserResponse{
UserId: human.AggregateID,
Details: obj_grpc.AddToDetailsPb(
human.Sequence,
human.ChangeDate,
human.ResourceOwner,
),
}, nil
}
func (s *Server) AddMachineUser(ctx context.Context, req *mgmt_pb.AddMachineUserRequest) (*mgmt_pb.AddMachineUserResponse, error) {
machine, err := s.command.AddMachine(ctx, authz.GetCtxData(ctx).OrgID, AddMachineUserRequestToDomain(req))
if err != nil {

View File

@ -69,6 +69,38 @@ func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human
return h
}
func ImportHumanUserRequestToDomain(req *mgmt_pb.ImportHumanUserRequest) *domain.Human {
h := &domain.Human{
Username: req.UserName,
}
preferredLanguage, err := language.Parse(req.Profile.PreferredLanguage)
logging.Log("MANAG-3GUFJ").OnError(err).Debug("language malformed")
h.Profile = &domain.Profile{
FirstName: req.Profile.FirstName,
LastName: req.Profile.LastName,
NickName: req.Profile.NickName,
DisplayName: req.Profile.DisplayName,
PreferredLanguage: preferredLanguage,
Gender: user_grpc.GenderToDomain(req.Profile.Gender),
}
h.Email = &domain.Email{
EmailAddress: req.Email.Email,
IsEmailVerified: req.Email.IsEmailVerified,
}
if req.Phone != nil {
h.Phone = &domain.Phone{
PhoneNumber: req.Phone.Phone,
IsPhoneVerified: req.Phone.IsPhoneVerified,
}
}
if req.Password != "" {
h.Password = &domain.Password{SecretString: req.Password}
h.Password.ChangeRequired = true
}
return h
}
func AddMachineUserRequestToDomain(req *mgmt_pb.AddMachineUserRequest) *domain.Machine {
return &domain.Machine{
Username: req.UserName,

View File

@ -8,9 +8,10 @@ import (
func ModelLabelPolicyToPb(policy *model.LabelPolicyView) *policy_pb.LabelPolicy {
return &policy_pb.LabelPolicy{
IsDefault: policy.Default,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
IsDefault: policy.Default,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Details: object.ToViewDetailsPb(
policy.Sequence,
policy.CreationDate,

View File

@ -244,7 +244,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if request.RequestedOrgID != "" && request.RequestedOrgID != user.ResourceOwner {
return errors.ThrowPreconditionFailed(nil, "EVENT-fJe2a", "Errors.User.NotAllowedOrg")
}
request.SetUserInfo(user.ID, user.PreferredLoginName, user.DisplayName, user.ResourceOwner)
request.SetUserInfo(user.ID, user.UserName, user.PreferredLoginName, user.DisplayName, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
@ -425,21 +425,30 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *domai
orgID = repo.IAMID
}
policy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)
loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)
if err != nil {
return err
}
request.LoginPolicy = policy
request.LoginPolicy = loginPolicy
if idpProviders != nil {
request.AllowedExternalIDPs = idpProviders
}
labelPolicy, err := repo.getLabelPolicy(ctx, orgID)
if err != nil {
return err
}
request.LabelPolicy = labelPolicy
return nil
}
func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain.AuthRequest, loginName string) (err error) {
user := new(user_view_model.UserView)
if request.RequestedOrgID != "" {
user, err = repo.View.UserByLoginNameAndResourceOwner(loginName, request.RequestedOrgID)
preferredLoginName := loginName
if request.RequestedOrgID != "" {
preferredLoginName += "@" + request.RequestedPrimaryDomain
}
user, err = repo.View.UserByLoginNameAndResourceOwner(preferredLoginName, request.RequestedOrgID)
} else {
user, err = repo.View.UserByLoginName(loginName)
if err == nil {
@ -453,7 +462,7 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *domain
return err
}
request.SetUserInfo(user.ID, loginName, "", user.ResourceOwner)
request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", user.ResourceOwner)
return nil
}
@ -496,7 +505,7 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(request *domain.AuthRequest,
if err != nil {
return err
}
request.SetUserInfo(externalIDP.UserID, "", "", externalIDP.ResourceOwner)
request.SetUserInfo(externalIDP.UserID, "", "", "", externalIDP.ResourceOwner)
return nil
}
@ -599,6 +608,7 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *domain.AuthRequest)
users[i] = domain.UserSelection{
UserID: session.UserID,
DisplayName: session.DisplayName,
UserName: session.UserName,
LoginName: session.LoginName,
UserSessionState: auth_req_model.UserSessionStateToDomain(session.State),
SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner,
@ -695,6 +705,21 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
return iam_es_model.LoginPolicyViewToModel(policy), err
}
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*domain.LabelPolicy, error) {
policy, err := repo.View.LabelPolicyByAggregateID(orgID)
if errors.IsNotFound(err) {
policy, err = repo.View.LabelPolicyByAggregateID(repo.IAMID)
if err != nil {
return nil, err
}
policy.Default = true
}
if err != nil {
return nil, err
}
return policy.ToDomain(), err
}
func setOrgID(orgViewProvider orgViewProvider, request *domain.AuthRequest) error {
primaryDomain := request.GetScopeOrgPrimaryDomain()
if primaryDomain == "" {
@ -707,6 +732,7 @@ func setOrgID(orgViewProvider orgViewProvider, request *domain.AuthRequest) erro
}
request.RequestedOrgID = org.ID
request.RequestedOrgName = org.Name
request.RequestedPrimaryDomain = primaryDomain
return nil
}

View File

@ -67,6 +67,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
newOrgIAMPolicy(
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newProjectRole(handler{view, bulkLimit, configs.cycleDuration("ProjectRole"), errorCount, es}),
newLabelPolicy(handler{view, bulkLimit, configs.cycleDuration("LabelPolicy"), errorCount, es}),
}
}

View File

@ -0,0 +1,104 @@
package handler
import (
"github.com/caos/logging"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/models"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/eventstore/v1/spooler"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
const (
labelPolicyTable = "auth.label_policies"
)
type LabelPolicy struct {
handler
subscription *v1.Subscription
}
func newLabelPolicy(handler handler) *LabelPolicy {
h := &LabelPolicy{
handler: handler,
}
h.subscribe()
return h
}
func (m *LabelPolicy) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
func (m *LabelPolicy) ViewModel() string {
return labelPolicyTable
}
func (_ *LabelPolicy) AggregateTypes() []models.AggregateType {
return []models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (m *LabelPolicy) CurrentSequence() (uint64, error) {
sequence, err := m.view.GetLatestLabelPolicySequence()
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *LabelPolicy) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestLabelPolicySequence()
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *LabelPolicy) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processLabelPolicy(event)
}
return err
}
func (m *LabelPolicy) processLabelPolicy(event *models.Event) (err error) {
policy := new(iam_model.LabelPolicyView)
switch event.Type {
case iam_es_model.LabelPolicyAdded, model.LabelPolicyAdded:
err = policy.AppendEvent(event)
case iam_es_model.LabelPolicyChanged, model.LabelPolicyChanged:
policy, err = m.view.LabelPolicyByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = policy.AppendEvent(event)
default:
return m.view.ProcessedLabelPolicySequence(event)
}
if err != nil {
return err
}
return m.view.PutLabelPolicy(policy, event)
}
func (m *LabelPolicy) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label policy handler")
return spooler.HandleError(event, err, m.view.GetLatestLabelPolicyFailedEvent, m.view.ProcessedLabelPolicyFailedEvent, m.view.ProcessedLabelPolicySequence, m.errorCountUntilSkip)
}
func (m *LabelPolicy) OnSuccess() error {
return spooler.HandleSuccess(m.view.UpdateLabelPolicySpoolerRunTimestamp)
}

View File

@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
labelPolicyTable = "auth.label_policies"
)
func (v *View) LabelPolicyByAggregateID(aggregateID string) (*model.LabelPolicyView, error) {
return view.GetLabelPolicyByAggregateID(v.Db, labelPolicyTable, aggregateID)
}
func (v *View) PutLabelPolicy(policy *model.LabelPolicyView, event *models.Event) error {
err := view.PutLabelPolicy(v.Db, labelPolicyTable, policy)
if err != nil {
return err
}
return v.ProcessedLabelPolicySequence(event)
}
func (v *View) DeleteLabelPolicy(aggregateID string, event *models.Event) error {
err := view.DeleteLabelPolicy(v.Db, labelPolicyTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedLabelPolicySequence(event)
}
func (v *View) GetLatestLabelPolicySequence() (*global_view.CurrentSequence, error) {
return v.latestSequence(labelPolicyTable)
}
func (v *View) ProcessedLabelPolicySequence(event *models.Event) error {
return v.saveCurrentSequence(labelPolicyTable, event)
}
func (v *View) UpdateLabelPolicySpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(labelPolicyTable)
}
func (v *View) GetLatestLabelPolicyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(labelPolicyTable, sequence)
}
func (v *View) ProcessedLabelPolicyFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -27,23 +27,26 @@ type AuthRequest struct {
MaxAuthAge uint32
Request Request
levelOfAssurance LevelOfAssurance
UserID string
LoginName string
DisplayName string
UserOrgID string
RequestedOrgID string
RequestedOrgName string
SelectedIDPConfigID string
LinkingUsers []*ExternalUser
PossibleSteps []NextStep
PasswordVerified bool
MFAsVerified []MFAType
Audience []string
AuthTime time.Time
Code string
LoginPolicy *model.LoginPolicyView
AllowedExternalIDPs []*model.IDPProviderView
levelOfAssurance LevelOfAssurance
UserID string
UserName string
LoginName string
DisplayName string
UserOrgID string
RequestedOrgID string
RequestedOrgName string
RequestedPrimaryDomain string
SelectedIDPConfigID string
LinkingUsers []*ExternalUser
PossibleSteps []NextStep
PasswordVerified bool
MFAsVerified []MFAType
Audience []string
AuthTime time.Time
Code string
LoginPolicy *model.LoginPolicyView
LabelPolicy *model.LabelPolicyView
AllowedExternalIDPs []*model.IDPProviderView
}
type ExternalUser struct {
@ -123,8 +126,9 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
return a
}
func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID string) {
func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, userOrgID string) {
a.UserID = userID
a.UserName = userName
a.LoginName = loginName
a.DisplayName = displayName
a.UserOrgID = userOrgID

View File

@ -54,6 +54,7 @@ func (s *SelectUserStep) Type() NextStepType {
type UserSelection struct {
UserID string
DisplayName string
UserName string
LoginName string
UserSessionState UserSessionState
SelectionPossible bool

View File

@ -46,9 +46,10 @@ func writeModelToLoginPolicy(wm *LoginPolicyWriteModel) *domain.LoginPolicy {
func writeModelToLabelPolicy(wm *LabelPolicyWriteModel) *domain.LabelPolicy {
return &domain.LabelPolicy{
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
PrimaryColor: wm.PrimaryColor,
SecondaryColor: wm.SecondaryColor,
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
PrimaryColor: wm.PrimaryColor,
SecondaryColor: wm.SecondaryColor,
HideLoginNameSuffix: wm.HideLoginNameSuffix,
}
}

View File

@ -40,7 +40,7 @@ func (c *Commands) addDefaultLabelPolicy(ctx context.Context, iamAgg *eventstore
return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-2B0ps", "Errors.IAM.LabelPolicy.AlreadyExists")
}
return iam_repo.NewLabelPolicyAddedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor), nil
return iam_repo.NewLabelPolicyAddedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix), nil
}
@ -57,7 +57,7 @@ func (c *Commands) ChangeDefaultLabelPolicy(ctx context.Context, policy *domain.
return nil, caos_errs.ThrowNotFound(nil, "IAM-0K9dq", "Errors.IAM.LabelPolicy.NotFound")
}
iamAgg := IAMAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, iamAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-4M9vs", "Errors.IAM.LabelPolicy.NotChanged")
}

View File

@ -53,6 +53,7 @@ func (wm *IAMLabelPolicyWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
) (*iam.LabelPolicyChangedEvent, bool) {
changes := make([]policy.LabelPolicyChanges, 0)
if wm.PrimaryColor != primaryColor {
@ -61,6 +62,9 @@ func (wm *IAMLabelPolicyWriteModel) NewChangedEvent(
if wm.SecondaryColor != secondaryColor {
changes = append(changes, policy.ChangeSecondaryColor(secondaryColor))
}
if wm.HideLoginNameSuffix != hideLoginNameSuffix {
changes = append(changes, policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix))
}
if len(changes) == 0 {
return nil, false
}

View File

@ -60,6 +60,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
@ -68,8 +69,9 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -89,6 +91,7 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
true,
),
),
},
@ -98,8 +101,9 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -108,8 +112,9 @@ func TestCommandSide_AddDefaultLabelPolicy(t *testing.T) {
AggregateID: "IAM",
ResourceOwner: "IAM",
},
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
},
@ -199,6 +204,7 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
@ -207,8 +213,9 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -226,13 +233,14 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
&iam.NewAggregate().Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultLabelPolicyChangedEvent(context.Background(), "primary-color-change", "secondary-color-change"),
newDefaultLabelPolicyChangedEvent(context.Background(), "primary-color-change", "secondary-color-change", false),
),
},
),
@ -241,8 +249,9 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
args: args{
ctx: context.Background(),
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
HideLoginNameSuffix: false,
},
},
res: res{
@ -251,8 +260,9 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
AggregateID: "IAM",
ResourceOwner: "IAM",
},
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
HideLoginNameSuffix: false,
},
},
},
@ -276,12 +286,13 @@ func TestCommandSide_ChangeDefaultLabelPolicy(t *testing.T) {
}
}
func newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, secondaryColor string) *iam.LabelPolicyChangedEvent {
func newDefaultLabelPolicyChangedEvent(ctx context.Context, primaryColor, secondaryColor string, hideLoginNameSuffix bool) *iam.LabelPolicyChangedEvent {
event, _ := iam.NewLabelPolicyChangedEvent(ctx,
&iam.NewAggregate().Aggregate,
[]policy.LabelPolicyChanges{
policy.ChangePrimaryColor(primaryColor),
policy.ChangeSecondaryColor(secondaryColor),
policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix),
},
)
return event

View File

@ -25,7 +25,7 @@ func (c *Commands) AddLabelPolicy(ctx context.Context, resourceOwner string, pol
}
orgAgg := OrgAggregateFromWriteModel(&addedPolicy.LabelPolicyWriteModel.WriteModel)
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyAddedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor))
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyAddedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix))
if err != nil {
return nil, err
}
@ -53,7 +53,7 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.LabelPolicyWriteModel.WriteModel)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor)
changedEvent, hasChanged := existingPolicy.NewChangedEvent(ctx, orgAgg, policy.PrimaryColor, policy.SecondaryColor, policy.HideLoginNameSuffix)
if !hasChanged {
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-4M9vs", "Errors.Org.LabelPolicy.NotChanged")
}
@ -69,19 +69,26 @@ func (c *Commands) ChangeLabelPolicy(ctx context.Context, resourceOwner string,
return writeModelToLabelPolicy(&existingPolicy.LabelPolicyWriteModel), nil
}
func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) error {
func (c *Commands) RemoveLabelPolicy(ctx context.Context, orgID string) (*domain.ObjectDetails, error) {
if orgID == "" {
return caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-Mf9sf", "Errors.ResourceOwnerMissing")
}
existingPolicy := NewOrgLabelPolicyWriteModel(orgID)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicy)
if err != nil {
return err
return nil, err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LabelPolicy.NotFound")
return nil, caos_errs.ThrowNotFound(nil, "Org-3M9df", "Errors.Org.LabelPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.WriteModel)
_, err = c.eventstore.PushEvents(ctx, org.NewLabelPolicyRemovedEvent(ctx, orgAgg))
return err
pushedEvents, err := c.eventstore.PushEvents(ctx, org.NewLabelPolicyRemovedEvent(ctx, orgAgg))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingPolicy, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingPolicy.LabelPolicyWriteModel.WriteModel), nil
}

View File

@ -52,6 +52,7 @@ func (wm *OrgLabelPolicyWriteModel) NewChangedEvent(
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
) (*org.LabelPolicyChangedEvent, bool) {
changes := make([]policy.LabelPolicyChanges, 0)
if wm.PrimaryColor != primaryColor {
@ -60,6 +61,9 @@ func (wm *OrgLabelPolicyWriteModel) NewChangedEvent(
if wm.SecondaryColor != secondaryColor {
changes = append(changes, policy.ChangeSecondaryColor(secondaryColor))
}
if wm.HideLoginNameSuffix != hideLoginNameSuffix {
changes = append(changes, policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix))
}
if len(changes) == 0 {
return nil, false
}

View File

@ -82,6 +82,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
@ -91,8 +92,9 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) {
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -112,6 +114,7 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
true,
),
),
},
@ -122,8 +125,9 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) {
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -132,8 +136,9 @@ func TestCommandSide_AddLabelPolicy(t *testing.T) {
AggregateID: "org1",
ResourceOwner: "org1",
},
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
},
@ -244,6 +249,7 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
@ -253,8 +259,9 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
PrimaryColor: "primary-color",
SecondaryColor: "secondary-color",
HideLoginNameSuffix: true,
},
},
res: res{
@ -272,13 +279,14 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newLabelPolicyChangedEvent(context.Background(), "org1", "primary-color-change", "secondary-color-change"),
newLabelPolicyChangedEvent(context.Background(), "org1", "primary-color-change", "secondary-color-change", false),
),
},
),
@ -288,8 +296,9 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
ctx: context.Background(),
orgID: "org1",
policy: &domain.LabelPolicy{
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
HideLoginNameSuffix: false,
},
},
res: res{
@ -298,8 +307,9 @@ func TestCommandSide_ChangeLabelPolicy(t *testing.T) {
AggregateID: "org1",
ResourceOwner: "org1",
},
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
PrimaryColor: "primary-color-change",
SecondaryColor: "secondary-color-change",
HideLoginNameSuffix: false,
},
},
},
@ -381,6 +391,7 @@ func TestCommandSide_RemoveLabelPolicy(t *testing.T) {
&org.NewAggregate("org1", "org1").Aggregate,
"primary-color",
"secondary-color",
true,
),
),
),
@ -406,7 +417,7 @@ func TestCommandSide_RemoveLabelPolicy(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
err := r.RemoveLabelPolicy(tt.args.ctx, tt.args.orgID)
_, err := r.RemoveLabelPolicy(tt.args.ctx, tt.args.orgID)
if tt.res.err == nil {
assert.NoError(t, err)
}
@ -417,12 +428,13 @@ func TestCommandSide_RemoveLabelPolicy(t *testing.T) {
}
}
func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, secondaryColor string) *org.LabelPolicyChangedEvent {
func newLabelPolicyChangedEvent(ctx context.Context, orgID, primaryColor, secondaryColor string, hideLoginNameSuffix bool) *org.LabelPolicyChangedEvent {
event, _ := org.NewLabelPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.LabelPolicyChanges{
policy.ChangePrimaryColor(primaryColor),
policy.ChangeSecondaryColor(secondaryColor),
policy.ChangeHideLoginNameSuffix(hideLoginNameSuffix),
},
)
return event

View File

@ -9,8 +9,9 @@ import (
type LabelPolicyWriteModel struct {
eventstore.WriteModel
PrimaryColor string
SecondaryColor string
PrimaryColor string
SecondaryColor string
HideLoginNameSuffix bool
State domain.PolicyState
}
@ -21,6 +22,7 @@ func (wm *LabelPolicyWriteModel) Reduce() error {
case *policy.LabelPolicyAddedEvent:
wm.PrimaryColor = e.PrimaryColor
wm.SecondaryColor = e.SecondaryColor
wm.HideLoginNameSuffix = e.HideLoginNameSuffix
wm.State = domain.PolicyStateActive
case *policy.LabelPolicyChangedEvent:
if e.PrimaryColor != nil {
@ -29,6 +31,9 @@ func (wm *LabelPolicyWriteModel) Reduce() error {
if e.SecondaryColor != nil {
wm.SecondaryColor = *e.SecondaryColor
}
if e.HideLoginNameSuffix != nil {
wm.HideLoginNameSuffix = *e.HideLoginNameSuffix
}
case *policy.LabelPolicyRemovedEvent:
wm.State = domain.PolicyStateRemoved
}

View File

@ -50,7 +50,46 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
return writeModelToHuman(addedHuman), nil
}
func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.Human) (*domain.Human, error) {
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
}
orgIAMPolicy, err := c.getOrgIAMPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.OrgIAMPolicy.NotFound")
}
pwPolicy, err := c.getOrgPasswordComplexityPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexity.NotFound")
}
events, addedHuman, err := c.importHuman(ctx, orgID, human, orgIAMPolicy, pwPolicy)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(addedHuman, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToHuman(addedHuman), nil
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, orgIAMPolicy, pwPolicy)
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.OrgIAMPolicy, pwPolicy *domain.PasswordComplexityPolicy) ([]eventstore.EventPusher, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid")
}
@ -104,6 +143,9 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexity.NotFound")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = false
}
return c.createHuman(ctx, orgID, human, externalIDP, true, orgIAMPolicy, pwPolicy)
}
@ -117,9 +159,12 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
}
human.AggregateID = userID
human.SetNamesAsDisplayname()
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, !selfregister); err != nil {
return nil, nil, err
if human.Password != nil {
if err := human.HashPasswordIfExisting(pwPolicy, c.userPasswordAlg, human.ChangeRequired); err != nil {
return nil, nil, err
}
}
addedHuman := NewHumanWriteModel(human.AggregateID, orgID)
//TODO: adlerhurst maybe we could simplify the code below
userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel)

View File

@ -171,51 +171,6 @@ func TestCommandSide_AddHuman(t *testing.T) {
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "org policy check failed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "email@test.ch",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "add human (with initial code), ok",
fields: fields{
@ -695,6 +650,553 @@ func TestCommandSide_AddHuman(t *testing.T) {
}
}
func TestCommandSide_ImportHuman(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretGenerator crypto.Generator
userPasswordAlg crypto.HashAlgorithm
}
type args struct {
ctx context.Context
orgID string
human *domain.Human
}
type res struct {
want *domain.Human
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "orgid missing, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
orgID: "",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "org policy not found, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "password policy not found, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(),
expectFilter(),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "user invalid, invalid argument error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
},
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "add human (with password and initial code), ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", true, ""),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Password: &domain.Password{
SecretString: "password",
ChangeRequired: true,
},
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
},
},
res: res{
want: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.Und,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
State: domain.UserStateInitial,
},
},
},
{
name: "add human email verified password change not required, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, ""),
),
eventFromEventPusher(
user.NewHumanEmailVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Password: &domain.Password{
SecretString: "password",
ChangeRequired: false,
},
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
},
},
res: res{
want: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.Und,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
IsEmailVerified: true,
},
State: domain.UserStateActive,
},
},
},
{
name: "add human (with phone), ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
eventFromEventPusher(
user.NewHumanPhoneCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1)),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Password: &domain.Password{
SecretString: "password",
ChangeRequired: false,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Phone: &domain.Phone{
PhoneNumber: "+41711234567",
},
},
},
res: res{
want: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.Und,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Phone: &domain.Phone{
PhoneNumber: "+41711234567",
},
State: domain.UserStateInitial,
},
},
},
{
name: "add human (with verified phone), ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusher(
newAddHumanEvent("password", false, "+41711234567"),
),
eventFromEventPusher(
user.NewHumanInitialCodeAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
time.Hour*1,
),
),
eventFromEventPusher(
user.NewHumanPhoneVerifiedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate),
),
},
uniqueConstraintsFromEventConstraint(user.NewAddUsernameUniqueConstraint("username", "org1", true)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "user1"),
secretGenerator: GetMockSecretGenerator(t),
userPasswordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Password: &domain.Password{
SecretString: "password",
ChangeRequired: false,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Phone: &domain.Phone{
PhoneNumber: "+41711234567",
IsPhoneVerified: true,
},
},
},
res: res{
want: &domain.Human{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
Username: "username",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
DisplayName: "firstname lastname",
PreferredLanguage: language.Und,
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Phone: &domain.Phone{
PhoneNumber: "+41711234567",
},
State: domain.UserStateInitial,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
initializeUserCode: tt.fields.secretGenerator,
phoneVerificationCode: tt.fields.secretGenerator,
userPasswordAlg: tt.fields.userPasswordAlg,
}
got, err := r.ImportHuman(tt.args.ctx, tt.args.orgID, tt.args.human)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_RegisterHuman(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -836,54 +1338,6 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "org policy check failed, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewOrgIAMPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
),
),
expectFilter(
eventFromEventPusher(
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
1,
false,
false,
false,
false,
),
),
),
),
},
args: args{
ctx: context.Background(),
orgID: "org1",
human: &domain.Human{
Username: "email@test.ch",
Profile: &domain.Profile{
FirstName: "firstname",
LastName: "lastname",
},
Email: &domain.Email{
EmailAddress: "email@test.ch",
},
Password: &domain.Password{
SecretString: "password",
},
},
},
res: res{
err: caos_errs.IsPreconditionFailed,
},
},
{
name: "add human (with password and initial code), ok",
fields: fields{

View File

@ -1,10 +1,12 @@
package domain
import (
"github.com/caos/zitadel/internal/errors"
"golang.org/x/text/language"
"strings"
"time"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/errors"
)
type AuthRequest struct {
@ -23,23 +25,26 @@ type AuthRequest struct {
MaxAuthAge uint32
Request Request
levelOfAssurance LevelOfAssurance
UserID string
LoginName string
DisplayName string
UserOrgID string
RequestedOrgID string
RequestedOrgName string
SelectedIDPConfigID string
LinkingUsers []*ExternalUser
PossibleSteps []NextStep
PasswordVerified bool
MFAsVerified []MFAType
Audience []string
AuthTime time.Time
Code string
LoginPolicy *LoginPolicy
AllowedExternalIDPs []*IDPProvider
levelOfAssurance LevelOfAssurance
UserID string
UserName string
LoginName string
DisplayName string
UserOrgID string
RequestedOrgID string
RequestedOrgName string
RequestedPrimaryDomain string
SelectedIDPConfigID string
LinkingUsers []*ExternalUser
PossibleSteps []NextStep
PasswordVerified bool
MFAsVerified []MFAType
Audience []string
AuthTime time.Time
Code string
LoginPolicy *LoginPolicy
AllowedExternalIDPs []*IDPProvider
LabelPolicy *LabelPolicy
}
type ExternalUser struct {
@ -103,8 +108,9 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
return a
}
func (a *AuthRequest) SetUserInfo(userID, loginName, displayName, userOrgID string) {
func (a *AuthRequest) SetUserInfo(userID, userName, loginName, displayName, userOrgID string) {
a.UserID = userID
a.UserName = userName
a.LoginName = loginName
a.DisplayName = displayName
a.UserOrgID = userOrgID

View File

@ -1,11 +1,11 @@
package domain
import (
"time"
"github.com/caos/zitadel/internal/crypto"
caos_errors "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
"strings"
"time"
)
type Human struct {
@ -59,9 +59,6 @@ func (u *Human) CheckOrgIAMPolicy(policy *OrgIAMPolicy) error {
if policy == nil {
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.OrgIamPolicyNil")
}
if policy.UserLoginMustBeDomain && strings.Contains(u.Username, "@") {
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-se4sJ", "Errors.User.EmailAsUsernameNotAllowed")
}
if !policy.UserLoginMustBeDomain && u.Profile != nil && u.Username == "" && u.Email != nil {
u.Username = u.EmailAddress
}

View File

@ -42,6 +42,7 @@ func (s *SelectUserStep) Type() NextStepType {
type UserSelection struct {
UserID string
UserName string
DisplayName string
LoginName string
UserSessionState UserSessionState

View File

@ -7,9 +7,10 @@ import (
type LabelPolicy struct {
models.ObjectRoot
Default bool
PrimaryColor string
SecondaryColor string
Default bool
PrimaryColor string
SecondaryColor string
HideLoginNameSuffix bool
}
func (p *LabelPolicy) IsValid() bool {

View File

@ -7,10 +7,11 @@ import (
type LabelPolicy struct {
models.ObjectRoot
State PolicyState
Default bool
PrimaryColor string
SecondaryColor string
State PolicyState
Default bool
PrimaryColor string
SecondaryColor string
HideLoginNameSuffix bool
}
func (p *LabelPolicy) IsValid() bool {

View File

@ -6,10 +6,11 @@ import (
)
type LabelPolicyView struct {
AggregateID string
PrimaryColor string
SecondaryColor string
Default bool
AggregateID string
PrimaryColor string
SecondaryColor string
HideLoginNameSuffix bool
Default bool
CreationDate time.Time
ChangeDate time.Time

View File

@ -10,26 +10,29 @@ import (
type LabelPolicy struct {
es_models.ObjectRoot
State int32 `json:"-"`
PrimaryColor string `json:"primaryColor"`
SecondaryColor string `json:"secondaryColor"`
State int32 `json:"-"`
PrimaryColor string `json:"primaryColor"`
SecondaryColor string `json:"secondaryColor"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix"`
}
func LabelPolicyToModel(policy *LabelPolicy) *iam_model.LabelPolicy {
return &iam_model.LabelPolicy{
ObjectRoot: policy.ObjectRoot,
State: iam_model.PolicyState(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
ObjectRoot: policy.ObjectRoot,
State: iam_model.PolicyState(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}
func LabelPolicyFromModel(policy *iam_model.LabelPolicy) *LabelPolicy {
return &LabelPolicy{
ObjectRoot: policy.ObjectRoot,
State: int32(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
ObjectRoot: policy.ObjectRoot,
State: int32(policy.State),
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"time"
"github.com/caos/zitadel/internal/domain"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
@ -24,34 +25,39 @@ type LabelPolicyView struct {
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:label_policy_state"`
PrimaryColor string `json:"primaryColor" gorm:"column:primary_color"`
SecondaryColor string `json:"secondaryColor" gorm:"column:secondary_color"`
Default bool `json:"-" gorm:"-"`
PrimaryColor string `json:"primaryColor" gorm:"column:primary_color"`
SecondaryColor string `json:"secondaryColor" gorm:"column:secondary_color"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix" gorm:"column:hide_login_name_suffix"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func LabelPolicyViewFromModel(policy *model.LabelPolicyView) *LabelPolicyView {
return &LabelPolicyView{
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
Default: policy.Default,
func (p *LabelPolicyView) ToDomain() *domain.LabelPolicy {
return &domain.LabelPolicy{
ObjectRoot: models.ObjectRoot{
AggregateID: p.AggregateID,
CreationDate: p.CreationDate,
ChangeDate: p.ChangeDate,
Sequence: p.Sequence,
},
Default: p.Default,
PrimaryColor: p.PrimaryColor,
SecondaryColor: p.SecondaryColor,
HideLoginNameSuffix: p.HideLoginNameSuffix,
}
}
func LabelPolicyViewToModel(policy *LabelPolicyView) *model.LabelPolicyView {
return &model.LabelPolicyView{
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
Default: policy.Default,
AggregateID: policy.AggregateID,
Sequence: policy.Sequence,
CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Default: policy.Default,
}
}

View File

@ -199,6 +199,32 @@ func (repo *OrgRepository) GetLabelPolicy(ctx context.Context) (*iam_model.Label
return iam_es_model.LabelPolicyViewToModel(policy), err
}
func (repo *OrgRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, viewErr := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
if viewErr != nil && !errors.IsNotFound(viewErr) {
return nil, viewErr
}
if errors.IsNotFound(viewErr) {
policy = new(iam_es_model.LabelPolicyView)
}
events, esErr := repo.getIAMEvents(ctx, policy.Sequence)
if errors.IsNotFound(viewErr) && len(events) == 0 {
return nil, errors.ThrowNotFound(nil, "EVENT-3Nf8sd", "Errors.IAM.LabelPolicy.NotFound")
}
if esErr != nil {
logging.Log("EVENT-28uLp").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
}
policy.Default = true
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) {
policy, viewErr := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if viewErr != nil && !errors.IsNotFound(viewErr) {

View File

@ -45,4 +45,7 @@ type OrgRepository interface {
GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
}

View File

@ -22,6 +22,7 @@ func NewLabelPolicyAddedEvent(
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
) *LabelPolicyAddedEvent {
return &LabelPolicyAddedEvent{
LabelPolicyAddedEvent: *policy.NewLabelPolicyAddedEvent(
@ -30,7 +31,8 @@ func NewLabelPolicyAddedEvent(
aggregate,
LabelPolicyAddedEventType),
primaryColor,
secondaryColor),
secondaryColor,
hideLoginNameSuffix),
}
}

View File

@ -23,6 +23,7 @@ func NewLabelPolicyAddedEvent(
aggregate *eventstore.Aggregate,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
) *LabelPolicyAddedEvent {
return &LabelPolicyAddedEvent{
LabelPolicyAddedEvent: *policy.NewLabelPolicyAddedEvent(
@ -31,7 +32,8 @@ func NewLabelPolicyAddedEvent(
aggregate,
LabelPolicyAddedEventType),
primaryColor,
secondaryColor),
secondaryColor,
hideLoginNameSuffix),
}
}

View File

@ -16,8 +16,9 @@ const (
type LabelPolicyAddedEvent struct {
eventstore.BaseEvent `json:"-"`
PrimaryColor string `json:"primaryColor,omitempty"`
SecondaryColor string `json:"secondaryColor,omitempty"`
PrimaryColor string `json:"primaryColor,omitempty"`
SecondaryColor string `json:"secondaryColor,omitempty"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix,omitempty"`
}
func (e *LabelPolicyAddedEvent) Data() interface{} {
@ -32,12 +33,14 @@ func NewLabelPolicyAddedEvent(
base *eventstore.BaseEvent,
primaryColor,
secondaryColor string,
hideLoginNameSuffix bool,
) *LabelPolicyAddedEvent {
return &LabelPolicyAddedEvent{
BaseEvent: *base,
PrimaryColor: primaryColor,
SecondaryColor: secondaryColor,
BaseEvent: *base,
PrimaryColor: primaryColor,
SecondaryColor: secondaryColor,
HideLoginNameSuffix: hideLoginNameSuffix,
}
}
@ -57,8 +60,9 @@ func LabelPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReade
type LabelPolicyChangedEvent struct {
eventstore.BaseEvent `json:"-"`
PrimaryColor *string `json:"primaryColor,omitempty"`
SecondaryColor *string `json:"secondaryColor,omitempty"`
PrimaryColor *string `json:"primaryColor,omitempty"`
SecondaryColor *string `json:"secondaryColor,omitempty"`
HideLoginNameSuffix *bool `json:"hideLoginNameSuffix,omitempty"`
}
func (e *LabelPolicyChangedEvent) Data() interface{} {
@ -99,6 +103,12 @@ func ChangeSecondaryColor(secondaryColor string) func(*LabelPolicyChangedEvent)
}
}
func ChangeHideLoginNameSuffix(hideLoginNameSuffix bool) func(*LabelPolicyChangedEvent) {
return func(e *LabelPolicyChangedEvent) {
e.HideLoginNameSuffix = &hideLoginNameSuffix
}
}
func LabelPolicyChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
e := &LabelPolicyChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),

View File

@ -54,7 +54,8 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
return
}
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
err = l.authRepo.CheckLoginName(r.Context(), authReq.ID, data.LoginName, userAgentID)
loginName := data.LoginName
err = l.authRepo.CheckLoginName(r.Context(), authReq.ID, loginName, userAgentID)
if err != nil {
l.renderLogin(w, r, authReq, err)
return
@ -73,7 +74,7 @@ func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *dom
return authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowUsernamePassword
},
"hasExternalLogin": func() bool {
return authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0
return authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowExternalIDP && authReq.AllowedExternalIDPs != nil && len(authReq.AllowedExternalIDPs) > 0
},
}
l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLogin], data, funcs)

View File

@ -265,15 +265,17 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title
ErrType: errType,
ErrMessage: errMessage,
},
Lang: l.renderer.Lang(r).String(),
Title: title,
Theme: l.getTheme(r),
ThemeMode: l.getThemeMode(r),
OrgID: l.getOrgID(authReq),
OrgName: l.getOrgName(authReq),
AuthReqID: getRequestID(authReq, r),
CSRF: csrf.TemplateField(r),
Nonce: http_mw.GetNonce(r),
Lang: l.renderer.Lang(r).String(),
Title: title,
Theme: l.getTheme(r),
ThemeMode: l.getThemeMode(r),
OrgID: l.getOrgID(authReq),
OrgName: l.getOrgName(authReq),
PrimaryDomain: l.getOrgPrimaryDomain(authReq),
DisplayLoginNameSuffix: l.isDisplayLoginNameSuffix(authReq),
AuthReqID: getRequestID(authReq, r),
CSRF: csrf.TemplateField(r),
Nonce: http_mw.GetNonce(r),
}
if authReq != nil {
baseData.LoginPolicy = authReq.LoginPolicy
@ -283,12 +285,14 @@ func (l *Login) getBaseData(r *http.Request, authReq *domain.AuthRequest, title
}
func (l *Login) getProfileData(authReq *domain.AuthRequest) profileData {
var loginName, displayName string
var userName, loginName, displayName string
if authReq != nil {
userName = authReq.UserName
loginName = authReq.LoginName
displayName = authReq.DisplayName
}
return profileData{
UserName: userName,
LoginName: loginName,
DisplayName: displayName,
}
@ -329,6 +333,23 @@ func (l *Login) getOrgName(authReq *domain.AuthRequest) string {
return authReq.RequestedOrgName
}
func (l *Login) getOrgPrimaryDomain(authReq *domain.AuthRequest) string {
if authReq == nil {
return ""
}
return authReq.RequestedPrimaryDomain
}
func (l *Login) isDisplayLoginNameSuffix(authReq *domain.AuthRequest) bool {
if authReq == nil {
return false
}
if authReq.RequestedOrgID == "" {
return false
}
return authReq.LabelPolicy != nil && !authReq.LabelPolicy.HideLoginNameSuffix
}
func getRequestID(authReq *domain.AuthRequest, r *http.Request) string {
if authReq != nil {
return authReq.ID
@ -351,17 +372,19 @@ func (l *Login) cspErrorHandler(err error) http.Handler {
type baseData struct {
errorData
Lang string
Title string
Theme string
ThemeMode string
OrgID string
OrgName string
AuthReqID string
CSRF template.HTML
Nonce string
LoginPolicy *domain.LoginPolicy
IDPProviders []*domain.IDPProvider
Lang string
Title string
Theme string
ThemeMode string
OrgID string
OrgName string
PrimaryDomain string
DisplayLoginNameSuffix bool
AuthReqID string
CSRF template.HTML
Nonce string
LoginPolicy *domain.LoginPolicy
IDPProviders []*domain.IDPProvider
}
type errorData struct {
@ -380,6 +403,7 @@ type userData struct {
type profileData struct {
LoginName string
UserName string
DisplayName string
}

View File

@ -1,19 +1,10 @@
Password:
Title: Willkommen zurück!
Description: Gib deine Benutzerdaten ein.
Password: Passwort
MinLength: Mindestlänge
HasUppercase: Grossbuchstaben
HasLowercase: Kleinbuchstaben
HasNumber: Nummer
HasSymbol: Symbol
Login:
Title: Anmeldung
Description: Mit ZITADEL-Konto anmelden.
TitleLinking: Anmeldung für Benutzer Linking
DescriptionLinking: Gib deine Benutzerdaten ein um den externen Benutzer mit einem ZITADEL Benutzer zu linken.
Loginname: Loginname
UsernamePlaceHolder: username
LoginnamePlaceHolder: username@domain
ExternalLogin: Melde dich mit einem externen Benutzer an
MustBeMemberOfOrg: Der Benutzer muss der Organisation {{.OrgName}} angehören.
@ -28,6 +19,16 @@ UserSelection:
SessionState1: inaktiv
MustBeMemberOfOrg: Der Benutzer muss der Organisation {{.OrgName}} angehören.
Password:
Title: Willkommen zurück!
Description: Gib deine Benutzerdaten ein.
Password: Passwort
MinLength: Mindestlänge
HasUppercase: Grossbuchstaben
HasLowercase: Kleinbuchstaben
HasNumber: Nummer
HasSymbol: Symbol
UsernameChange:
Title: Usernamen ändern
Description: Wähle deinen neuen Benutzernamen

View File

@ -4,9 +4,10 @@ Login:
TitleLinking: Login for userlinking
DescriptionLinking: Enter your login data to link your external user with a ZITADEL user.
Loginname: Loginname
UsernamePlaceHolder: username
LoginnamePlaceHolder: username@domain
ExternalLogin: Login with an external user.
MustBeMemberOfOrg: The user must be mermber of the {{.OrgDomain}} organisation.
MustBeMemberOfOrg: The user must be mermber of the {{.OrgName}} organisation.
UserSelection:
Title: Select account
@ -16,7 +17,7 @@ UserSelection:
OtherUser: Other User
SessionState0: active
SessionState1: inactive
MustBeMemberOfOrg: The user must be mermber of the {{.OrgDomain}} organisation.
MustBeMemberOfOrg: The user must be mermber of the {{.OrgName}} organisation.
Password:
Title: Password

View File

@ -12,6 +12,11 @@ $lgn-container-margin: 0px auto 50px auto;
align-items: center;
border: none;
outline: none;
cursor: pointer;
&:disabled {
cursor: not-allowed;
}
.left {
padding: .5rem 1rem;

View File

@ -25,7 +25,6 @@
color: inherit;
background: transparent;
box-shadow: inset 0 -1px lgn-color($foreground, footer-line);
cursor: pointer;
&:hover {
$primary: map-get($config, primary);

View File

@ -440,6 +440,10 @@ i {
align-items: center;
border: none;
outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
}
.lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem;
@ -1298,6 +1302,10 @@ i {
align-items: center;
border: none;
outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
}
.lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem;

File diff suppressed because one or more lines are too long

View File

@ -440,6 +440,10 @@ i {
align-items: center;
border: none;
outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
}
.lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem;
@ -1202,7 +1206,6 @@ a:hover, a:active {
color: inherit;
background: transparent;
box-shadow: inset 0 -1px #303131;
cursor: pointer;
}
.lgn-account-selection .lgn-account:hover {
background-color: rgba(255, 255, 255, 0.02);
@ -1522,7 +1525,6 @@ a:hover, a:active {
color: inherit;
background: transparent;
box-shadow: inset 0 -1px #303131;
cursor: pointer;
}
.lgn-dark-theme .lgn-account-selection .lgn-account:hover {
background-color: rgba(255, 255, 255, 0.02);
@ -1836,7 +1838,6 @@ a:hover, a:active {
color: inherit;
background: transparent;
box-shadow: inset 0 -1px #e3e8ee;
cursor: pointer;
}
.lgn-light-theme .lgn-account-selection .lgn-account:hover {
background-color: rgba(0, 0, 0, 0.02);

File diff suppressed because one or more lines are too long

View File

@ -20,8 +20,13 @@
{{if hasUsernamePasswordLogin }}
<div class="fields">
<label class="lgn-label" for="loginName">{{t "Login.Loginname"}}</label>
<input class="lgn-input lgn-suffix-input" type="text" id="loginName" name="loginName" placeholder="{{t "Login.LoginnamePlaceHolder"}}"
value="{{ .LoginName }}" {{if .ErrMessage}}shake {{end}} autocomplete="username" autofocus required>
<div class="lgn-suffix-wrapper">
<input class="lgn-input lgn-suffix-input" type="text" id="loginName" name="loginName" placeholder="{{if .OrgID }}{{t "Login.UsernamePlaceHolder"}}{{else}}{{t "Login.LoginnamePlaceHolder"}}{{end}}"
value="{{ .UserName }}" {{if .ErrMessage}}shake {{end}} autocomplete="username" autofocus required>
{{if .DisplayLoginNameSuffix}}
<span id="default-login-suffix" lgnsuffix class="loginname-suffix">@{{.PrimaryDomain}}</span>
{{end}}
</div>
</div>
{{end}}
@ -53,5 +58,6 @@
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
<script src="{{ resourceUrl "scripts/input_suffix_offset.js" }}"></script>
{{template "main-bottom" .}}

View File

@ -23,10 +23,11 @@
<div class="lgn-account-selection">
{{ if .Users }}
{{ $displayLoginNameSuffix := and .OrgID (not .DisplayLoginNameSuffix)}}
{{ range $user := .Users }}
{{ $sessionState := (printf "UserSelection.SessionState%v" $user.UserSessionState) }}
<button type="submit" name="userID" value="{{$user.UserID}}" class="lgn-account"
{{if not $user.SelectionPossible}}disabled title="{{t " Errors.User.NotAllowedOrg"}}"{{end}}>
{{if not $user.SelectionPossible}}disabled title="{{t "Errors.User.NotAllowedOrg"}}"{{end}}>
<div class="left">
<div class="lgn-avatar" displayname="{{$user.DisplayName}}">
<span class="initials">A</span>
@ -34,7 +35,7 @@
</div>
<div class="lgn-names">
<p class="lgn-displayname">{{$user.DisplayName}}</p>
<p class="lgn-loginname">{{$user.LoginName}}</p>
<p class="lgn-loginname">{{if and $displayLoginNameSuffix $user.SelectionPossible}}{{$user.UserName}}{{else}}{{$user.LoginName}}{{end}}</p>
<p class="lgn-session-state i{{$user.UserSessionState}}">{{t $sessionState}}</p>
</div>
<span class="fill-space"></span>

View File

@ -1,5 +1,5 @@
{{define "user-profile"}}
{{if .LoginName}}
{{if or .LoginName .UserName}}
<div class="lgn-login-profile">
<div class="lgn-profile-image"></div>
<div class="lgn-names">
@ -9,7 +9,7 @@
</div>
</div>
<div class="lgn-loginname">
<p>{{.LoginName}}</p>
<p>{{if .DisplayLoginNameSuffix}}{{.LoginName}}{{else}}{{.UserName}}{{end}}</p>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
ALTER TABLE management.label_policies ADD COLUMN hide_login_name_suffix BOOLEAN;
ALTER TABLE adminapi.label_policies ADD COLUMN hide_login_name_suffix BOOLEAN;
CREATE TABLE auth.label_policies (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
label_policy_state SMALLINT,
sequence BIGINT,
primary_color TEXT,
secondary_color TEXT,
hide_login_name_suffix BOOLEAN,
PRIMARY KEY (aggregate_id)
);
GRANT SELECT ON TABLE auth.label_policies TO notification;

View File

@ -1,7 +0,0 @@
CREATE TABLE eventstore.unique_constraints (
unique_type TEXT,
unique_field TEXT,
PRIMARY KEY (unique_type, unique_field)
);
GRANT DELETE ON TABLE eventstore.unique_constraints to eventstore;

View File

@ -0,0 +1,23 @@
CREATE TABLE eventstore.unique_constraints (
unique_type TEXT,
unique_field TEXT,
PRIMARY KEY (unique_type, unique_field)
);
GRANT DELETE ON TABLE eventstore.unique_constraints to eventstore;
ALTER TABLE management.login_policies ADD COLUMN default_policy BOOLEAN;
ALTER TABLE adminapi.login_policies ADD COLUMN default_policy BOOLEAN;
ALTER TABLE auth.login_policies ADD COLUMN default_policy BOOLEAN;
CREATE INDEX event_type ON eventstore.events (event_type);
CREATE INDEX resource_owner ON eventstore.events (resource_owner);
CREATE USER queries WITH PASSWORD ${queriespassword};
GRANT SELECT ON TABLE eventstore.events TO queries;
ALTER TABLE management.org_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE management.project_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE management.project_grant_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE adminapi.iam_members ADD COLUMN preferred_login_name TEXT;

View File

@ -1,3 +0,0 @@
ALTER TABLE management.login_policies ADD COLUMN default_policy BOOLEAN;
ALTER TABLE adminapi.login_policies ADD COLUMN default_policy BOOLEAN;
ALTER TABLE auth.login_policies ADD COLUMN default_policy BOOLEAN;

View File

@ -1,2 +0,0 @@
CREATE INDEX event_type ON eventstore.events (event_type);
CREATE INDEX resource_owner ON eventstore.events (resource_owner);

View File

@ -1,2 +0,0 @@
CREATE USER queries WITH PASSWORD ${queriespassword};
GRANT SELECT ON TABLE eventstore.events TO queries;

View File

@ -1,5 +0,0 @@
ALTER TABLE management.org_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE management.project_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE management.project_grant_members ADD COLUMN preferred_login_name TEXT;
ALTER TABLE adminapi.iam_members ADD COLUMN preferred_login_name TEXT;

View File

@ -755,6 +755,7 @@ message GetLabelPolicyResponse {
message UpdateLabelPolicyRequest {
string primary_color = 1 [(validate.rules).string = {min_len: 1, max_len: 50}];
string secondary_color = 2 [(validate.rules).string = {min_len: 1, max_len: 50}];
bool hide_login_name_suffix = 3;
}
message UpdateLabelPolicyResponse {

View File

@ -162,6 +162,17 @@ service ManagementService {
};
}
rpc ImportHumanUser(ImportHumanUserRequest) returns (ImportHumanUserResponse) {
option (google.api.http) = {
post: "/users/human/_import"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "user.write"
};
}
rpc AddMachineUser(AddMachineUserRequest) returns (AddMachineUserResponse) {
option (google.api.http) = {
post: "/users/machine"
@ -1641,6 +1652,58 @@ service ManagementService {
};
}
rpc GetLabelPolicy(GetLabelPolicyRequest) returns (GetLabelPolicyResponse) {
option (google.api.http) = {
get: "/policies/label"
};
option (zitadel.v1.auth_option) = {
permission: "policy.read"
};
}
rpc GetDefaultLabelPolicy(GetDefaultLabelPolicyRequest) returns (GetDefaultLabelPolicyResponse) {
option (google.api.http) = {
get: "/policies/default/label"
};
option (zitadel.v1.auth_option) = {
permission: "policy.read"
};
}
rpc AddCustomLabelPolicy(AddCustomLabelPolicyRequest) returns (AddCustomLabelPolicyResponse) {
option (google.api.http) = {
post: "/policies/label"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "policy.write"
};
}
rpc UpdateCustomLabelPolicy(UpdateCustomLabelPolicyRequest) returns (UpdateCustomLabelPolicyResponse) {
option (google.api.http) = {
put: "/policies/label"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "policy.write"
};
}
rpc ResetLabelPolicyToDefault(ResetLabelPolicyToDefaultRequest) returns (ResetLabelPolicyToDefaultResponse) {
option (google.api.http) = {
delete: "/policies/label"
};
option (zitadel.v1.auth_option) = {
permission: "policy.delete"
};
}
rpc GetOrgIDPByID(GetOrgIDPByIDRequest) returns (GetOrgIDPByIDResponse) {
option (google.api.http) = {
get: "/idps/{id}"
@ -1825,6 +1888,39 @@ message AddHumanUserResponse {
zitadel.v1.ObjectDetails details = 2;
}
message ImportHumanUserRequest {
message Profile {
string first_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string last_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string nick_name = 3 [(validate.rules).string = {max_len: 200}];
string display_name = 4 [(validate.rules).string = {max_len: 200}];
string preferred_language = 5 [(validate.rules).string = {max_len: 10}];
zitadel.user.v1.Gender gender = 6;
}
message Email {
string email = 1 [(validate.rules).string.email = true]; //TODO: check if no value is allowed
bool is_email_verified = 2;
}
message Phone {
// has to be a global number
string phone = 1 [(validate.rules).string = {min_len: 1, max_len: 50, prefix: "+"}];
bool is_phone_verified = 2;
}
string user_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
Profile profile = 2 [(validate.rules).message.required = true];
Email email = 3 [(validate.rules).message.required = true];
Phone phone = 4;
string password = 5;
bool password_change_required = 6;
}
message ImportHumanUserResponse {
string user_id = 1;
zitadel.v1.ObjectDetails details = 2;
}
message AddMachineUserRequest {
string user_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
@ -3097,6 +3193,45 @@ message ResetPasswordLockoutPolicyToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetLabelPolicyRequest {}
message GetLabelPolicyResponse {
zitadel.policy.v1.LabelPolicy policy = 1;
bool is_default = 2;
}
message GetDefaultLabelPolicyRequest {}
message GetDefaultLabelPolicyResponse {
zitadel.policy.v1.LabelPolicy policy = 1;
}
message AddCustomLabelPolicyRequest {
string primary_color = 1 [(validate.rules).string = {min_len: 1, max_len: 50}];
string secondary_color = 2 [(validate.rules).string = {min_len: 1, max_len: 50}];
bool hide_login_name_suffix = 3;
}
message AddCustomLabelPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}
message UpdateCustomLabelPolicyRequest {
string primary_color = 1 [(validate.rules).string = {min_len: 1, max_len: 50}];
string secondary_color = 2 [(validate.rules).string = {min_len: 1, max_len: 50}];
bool hide_login_name_suffix = 3;
}
message UpdateCustomLabelPolicyResponse {
zitadel.v1.ObjectDetails details = 1;
}
message ResetLabelPolicyToDefaultRequest {}
message ResetLabelPolicyToDefaultResponse {
zitadel.v1.ObjectDetails details = 1;
}
message GetOrgIDPByIDRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}

View File

@ -17,6 +17,7 @@ message LabelPolicy {
string primary_color = 2;
string secondary_color = 3;
bool is_default = 4;
bool hide_login_name_suffix = 5;
}
message LoginPolicy {