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>
This commit is contained in:
Fabi 2021-03-25 10:50:32 +01:00 committed by GitHub
parent a012aed5cb
commit 03ddb8fc38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 718 additions and 115 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/caos/logging" "github.com/caos/logging"
admin_model "github.com/caos/zitadel/internal/admin/model" admin_model "github.com/caos/zitadel/internal/admin/model"
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view" admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
@ -58,7 +59,7 @@ func (repo *OrgRepo) SetUpOrg(ctx context.Context, setUp *admin_model.SetupOrg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
user, userAggregates, err := repo.UserEventstore.PrepareCreateUser(ctx, setUp.User, pwPolicyView, orgPolicy, org.AggregateID) user, userAggregates, err := repo.UserEventstore.PrepareCreateUser(ctx, setUp.User, pwPolicyView, orgPolicy, true, org.AggregateID)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -11,6 +11,7 @@ func labelPolicyToModel(policy *admin.DefaultLabelPolicyUpdate) *iam_model.Label
return &iam_model.LabelPolicy{ return &iam_model.LabelPolicy{
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
} }
} }
@ -24,6 +25,7 @@ func labelPolicyFromModel(policy *iam_model.LabelPolicy) *admin.DefaultLabelPoli
return &admin.DefaultLabelPolicy{ return &admin.DefaultLabelPolicy{
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
CreationDate: creationDate, CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
} }
@ -39,6 +41,7 @@ func labelPolicyViewFromModel(policy *iam_model.LabelPolicyView) *admin.DefaultL
return &admin.DefaultLabelPolicyView{ return &admin.DefaultLabelPolicyView{
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
CreationDate: creationDate, CreationDate: creationDate,
ChangeDate: changeDate, ChangeDate: changeDate,
} }

View File

@ -0,0 +1,45 @@
package management
import (
"context"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetLabelPolicy(ctx context.Context, _ *empty.Empty) (*management.LabelPolicyView, error) {
result, err := s.org.GetLabelPolicy(ctx)
if err != nil {
return nil, err
}
return labelPolicyViewFromModel(result), nil
}
func (s *Server) GetDefaultLabelPolicy(ctx context.Context, _ *empty.Empty) (*management.LabelPolicyView, error) {
result, err := s.org.GetLabelPolicy(ctx)
if err != nil {
return nil, err
}
return labelPolicyViewFromModel(result), nil
}
func (s *Server) CreateLabelPolicy(ctx context.Context, policy *management.LabelPolicyRequest) (*management.LabelPolicy, error) {
result, err := s.org.AddLabelPolicy(ctx, labelPolicyRequestToModel(policy))
if err != nil {
return nil, err
}
return labelPolicyFromModel(result), nil
}
func (s *Server) UpdateLabelPolicy(ctx context.Context, policy *management.LabelPolicyRequest) (*management.LabelPolicy, error) {
result, err := s.org.ChangeLabelPolicy(ctx, labelPolicyRequestToModel(policy))
if err != nil {
return nil, err
}
return labelPolicyFromModel(result), nil
}
func (s *Server) RemoveLabelPolicy(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
err := s.org.RemoveLabelPolicy(ctx)
return &empty.Empty{}, err
}

View File

@ -0,0 +1,50 @@
package management
import (
"github.com/caos/logging"
"github.com/golang/protobuf/ptypes"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/pkg/grpc/management"
)
func labelPolicyRequestToModel(policy *management.LabelPolicyRequest) *iam_model.LabelPolicy {
return &iam_model.LabelPolicy{
SecondaryColor: policy.SecondaryColor,
PrimaryColor: policy.PrimaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
}
}
func labelPolicyFromModel(policy *iam_model.LabelPolicy) *management.LabelPolicy {
creationDate, err := ptypes.TimestampProto(policy.CreationDate)
logging.Log("GRPC-2Fsm8").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(policy.ChangeDate)
logging.Log("GRPC-3Flo0").OnError(err).Debug("date parse failed")
return &management.LabelPolicy{
SecondaryColor: policy.SecondaryColor,
PrimaryColor: policy.PrimaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}
func labelPolicyViewFromModel(policy *iam_model.LabelPolicyView) *management.LabelPolicyView {
creationDate, err := ptypes.TimestampProto(policy.CreationDate)
logging.Log("GRPC-5Tsm8").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(policy.ChangeDate)
logging.Log("GRPC-8dJgs").OnError(err).Debug("date parse failed")
return &management.LabelPolicyView{
Default: policy.Default,
SecondaryColor: policy.SecondaryColor,
PrimaryColor: policy.PrimaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}

View File

@ -59,6 +59,14 @@ func (s *Server) CreateUser(ctx context.Context, in *management.CreateUserReques
return userFromModel(user), nil return userFromModel(user), nil
} }
func (s *Server) ImportHuman(ctx context.Context, in *management.ImportHumanRequest) (*management.UserResponse, error) {
user, err := s.user.ImportUser(ctx, humanImportToModel(in), in.PasswordChangeRequired)
if err != nil {
return nil, err
}
return userFromModel(user), nil
}
func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) { func (s *Server) DeactivateUser(ctx context.Context, in *management.UserID) (*management.UserResponse, error) {
user, err := s.user.DeactivateUser(ctx, in.Id) user, err := s.user.DeactivateUser(ctx, in.Id)
if err != nil { if err != nil {

View File

@ -60,6 +60,42 @@ func userCreateToModel(user *management.CreateUserRequest) *usr_model.User {
} }
} }
func humanImportToModel(user *management.ImportHumanRequest) *usr_model.User {
preferredLanguage, err := language.Parse(user.PreferredLanguage)
logging.Log("GRPC-cK5k2").OnError(err).Debug("language malformed")
human := &usr_model.Human{
Profile: &usr_model.Profile{
FirstName: user.FirstName,
LastName: user.LastName,
NickName: user.NickName,
PreferredLanguage: preferredLanguage,
Gender: genderToModel(user.Gender),
},
Email: &usr_model.Email{
EmailAddress: user.Email,
IsEmailVerified: user.IsEmailVerified,
},
Address: &usr_model.Address{
Country: user.Country,
Locality: user.Locality,
PostalCode: user.PostalCode,
Region: user.Region,
StreetAddress: user.StreetAddress,
},
}
if user.Password != "" {
human.Password = &usr_model.Password{SecretString: user.Password}
}
if user.Phone != "" {
human.Phone = &usr_model.Phone{PhoneNumber: user.Phone, IsPhoneVerified: user.IsPhoneVerified}
}
return &usr_model.User{
UserName: user.UserName,
Human: human,
}
}
func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password { func passwordRequestToModel(r *management.PasswordRequest) *usr_model.Password {
return &usr_model.Password{ return &usr_model.Password{
ObjectRoot: models.ObjectRoot{AggregateID: r.Id}, ObjectRoot: models.ObjectRoot{AggregateID: r.Id},

View File

@ -244,7 +244,7 @@ func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID, userAge
if request.RequestedOrgID != "" && request.RequestedOrgID != user.ResourceOwner { if request.RequestedOrgID != "" && request.RequestedOrgID != user.ResourceOwner {
return errors.ThrowPreconditionFailed(nil, "EVENT-fJe2a", "Errors.User.NotAllowedOrg") 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) return repo.AuthRequests.UpdateAuthRequest(ctx, request)
} }
@ -456,21 +456,30 @@ func (repo *AuthRequestRepo) fillLoginPolicy(ctx context.Context, request *model
orgID = repo.IAMID orgID = repo.IAMID
} }
policy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID) loginPolicy, idpProviders, err := repo.getLoginPolicyAndIDPProviders(ctx, orgID)
if err != nil { if err != nil {
return err return err
} }
request.LoginPolicy = policy request.LoginPolicy = loginPolicy
if idpProviders != nil { if idpProviders != nil {
request.AllowedExternalIDPs = idpProviders request.AllowedExternalIDPs = idpProviders
} }
labelPolicy, err := repo.getLabelPolicy(ctx, orgID)
if err != nil {
return err
}
request.LabelPolicy = labelPolicy
return nil return nil
} }
func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) { func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.AuthRequest, loginName string) (err error) {
user := new(user_view_model.UserView) user := new(user_view_model.UserView)
if request.RequestedOrgID != "" { 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 { } else {
user, err = repo.View.UserByLoginName(loginName) user, err = repo.View.UserByLoginName(loginName)
if err == nil { if err == nil {
@ -484,7 +493,7 @@ func (repo *AuthRequestRepo) checkLoginName(ctx context.Context, request *model.
return err return err
} }
request.SetUserInfo(user.ID, loginName, "", user.ResourceOwner) request.SetUserInfo(user.ID, loginName, user.PreferredLoginName, "", user.ResourceOwner)
return nil return nil
} }
@ -527,7 +536,7 @@ func (repo *AuthRequestRepo) checkExternalUserLogin(request *model.AuthRequest,
if err != nil { if err != nil {
return err return err
} }
request.SetUserInfo(externalIDP.UserID, "", "", externalIDP.ResourceOwner) request.SetUserInfo(externalIDP.UserID, "", "", "", externalIDP.ResourceOwner)
return nil return nil
} }
@ -630,6 +639,7 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) (
users[i] = model.UserSelection{ users[i] = model.UserSelection{
UserID: session.UserID, UserID: session.UserID,
DisplayName: session.DisplayName, DisplayName: session.DisplayName,
UserName: session.UserName,
LoginName: session.LoginName, LoginName: session.LoginName,
UserSessionState: session.State, UserSessionState: session.State,
SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner, SelectionPossible: request.RequestedOrgID == "" || request.RequestedOrgID == session.ResourceOwner,
@ -733,6 +743,21 @@ func (repo *AuthRequestRepo) getLoginPolicy(ctx context.Context, orgID string) (
return iam_es_model.LoginPolicyViewToModel(policy), err return iam_es_model.LoginPolicyViewToModel(policy), err
} }
func (repo *AuthRequestRepo) getLabelPolicy(ctx context.Context, orgID string) (*iam_model.LabelPolicyView, 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 iam_es_model.LabelPolicyViewToModel(policy), err
}
func setOrgID(orgViewProvider orgViewProvider, request *model.AuthRequest) error { func setOrgID(orgViewProvider orgViewProvider, request *model.AuthRequest) error {
primaryDomain := request.GetScopeOrgPrimaryDomain() primaryDomain := request.GetScopeOrgPrimaryDomain()
if primaryDomain == "" { if primaryDomain == "" {
@ -745,6 +770,7 @@ func setOrgID(orgViewProvider orgViewProvider, request *model.AuthRequest) error
} }
request.RequestedOrgID = org.ID request.RequestedOrgID = org.ID
request.RequestedOrgName = org.Name request.RequestedOrgName = org.Name
request.RequestedPrimaryDomain = primaryDomain
return nil return nil
} }

View File

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

View File

@ -0,0 +1,104 @@
package handler
import (
"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/query"
"github.com/caos/zitadel/internal/eventstore/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 *eventstore.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/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

@ -29,11 +29,13 @@ type AuthRequest struct {
levelOfAssurance LevelOfAssurance levelOfAssurance LevelOfAssurance
UserID string UserID string
UserName string
LoginName string LoginName string
DisplayName string DisplayName string
UserOrgID string UserOrgID string
RequestedOrgID string RequestedOrgID string
RequestedOrgName string RequestedOrgName string
RequestedPrimaryDomain string
SelectedIDPConfigID string SelectedIDPConfigID string
LinkingUsers []*ExternalUser LinkingUsers []*ExternalUser
PossibleSteps []NextStep PossibleSteps []NextStep
@ -43,6 +45,7 @@ type AuthRequest struct {
AuthTime time.Time AuthTime time.Time
Code string Code string
LoginPolicy *model.LoginPolicyView LoginPolicy *model.LoginPolicyView
LabelPolicy *model.LabelPolicyView
AllowedExternalIDPs []*model.IDPProviderView AllowedExternalIDPs []*model.IDPProviderView
} }
@ -123,8 +126,9 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
return a 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.UserID = userID
a.UserName = userName
a.LoginName = loginName a.LoginName = loginName
a.DisplayName = displayName a.DisplayName = displayName
a.UserOrgID = userOrgID a.UserOrgID = userOrgID

View File

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

View File

@ -11,6 +11,7 @@ type LabelPolicy struct {
Default bool Default bool
PrimaryColor string PrimaryColor string
SecondaryColor string SecondaryColor string
HideLoginNameSuffix bool
} }
func (p *LabelPolicy) IsValid() bool { func (p *LabelPolicy) IsValid() bool {

View File

@ -10,6 +10,7 @@ type LabelPolicyView struct {
AggregateID string AggregateID string
PrimaryColor string PrimaryColor string
SecondaryColor string SecondaryColor string
HideLoginNameSuffix bool
Default bool Default bool
CreationDate time.Time CreationDate time.Time

View File

@ -14,6 +14,7 @@ type LabelPolicy struct {
State int32 `json:"-"` State int32 `json:"-"`
PrimaryColor string `json:"primaryColor"` PrimaryColor string `json:"primaryColor"`
SecondaryColor string `json:"secondaryColor"` SecondaryColor string `json:"secondaryColor"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix"`
} }
func LabelPolicyToModel(policy *LabelPolicy) *iam_model.LabelPolicy { func LabelPolicyToModel(policy *LabelPolicy) *iam_model.LabelPolicy {
@ -22,6 +23,7 @@ func LabelPolicyToModel(policy *LabelPolicy) *iam_model.LabelPolicy {
State: iam_model.PolicyState(policy.State), State: iam_model.PolicyState(policy.State),
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
} }
} }
@ -31,6 +33,7 @@ func LabelPolicyFromModel(policy *iam_model.LabelPolicy) *LabelPolicy {
State: int32(policy.State), State: int32(policy.State),
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
} }
} }

View File

@ -26,6 +26,7 @@ type LabelPolicyView struct {
PrimaryColor string `json:"primaryColor" gorm:"column:primary_color"` PrimaryColor string `json:"primaryColor" gorm:"column:primary_color"`
SecondaryColor string `json:"secondaryColor" gorm:"column:secondary_color"` SecondaryColor string `json:"secondaryColor" gorm:"column:secondary_color"`
HideLoginNameSuffix bool `json:"hideLoginNameSuffix" gorm:"column:hide_login_name_suffix"`
Default bool `json:"-" gorm:"-"` Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"` Sequence uint64 `json:"-" gorm:"column:sequence"`
@ -39,6 +40,7 @@ func LabelPolicyViewFromModel(policy *model.LabelPolicyView) *LabelPolicyView {
ChangeDate: policy.ChangeDate, ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Default: policy.Default, Default: policy.Default,
} }
} }
@ -51,6 +53,7 @@ func LabelPolicyViewToModel(policy *LabelPolicyView) *model.LabelPolicyView {
ChangeDate: policy.ChangeDate, ChangeDate: policy.ChangeDate,
PrimaryColor: policy.PrimaryColor, PrimaryColor: policy.PrimaryColor,
SecondaryColor: policy.SecondaryColor, SecondaryColor: policy.SecondaryColor,
HideLoginNameSuffix: policy.HideLoginNameSuffix,
Default: policy.Default, Default: policy.Default,
} }
} }

View File

@ -326,6 +326,32 @@ func (repo *OrgRepository) GetLabelPolicy(ctx context.Context) (*iam_model.Label
return iam_es_model.LabelPolicyViewToModel(policy), err 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.IAMEventstore.IAMEventsByID(ctx, repo.SystemDefaults.IamID, 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) AddLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) { func (repo *OrgRepository) AddLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) {
policy.AggregateID = authz.GetCtxData(ctx).OrgID policy.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.AddLabelPolicy(ctx, policy) return repo.OrgEventstore.AddLabelPolicy(ctx, policy)
@ -336,6 +362,13 @@ func (repo *OrgRepository) ChangeLabelPolicy(ctx context.Context, policy *iam_mo
return repo.OrgEventstore.ChangeLabelPolicy(ctx, policy) return repo.OrgEventstore.ChangeLabelPolicy(ctx, policy)
} }
func (repo *OrgRepository) RemoveLabelPolicy(ctx context.Context) error {
policy := &iam_model.LabelPolicy{ObjectRoot: models.ObjectRoot{
AggregateID: authz.GetCtxData(ctx).OrgID,
}}
return repo.OrgEventstore.RemoveLabelPolicy(ctx, policy)
}
func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) { func (repo *OrgRepository) GetLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) {
policy, viewErr := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID) policy, viewErr := repo.View.LoginPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if viewErr != nil && !errors.IsNotFound(viewErr) { if viewErr != nil && !errors.IsNotFound(viewErr) {

View File

@ -82,6 +82,26 @@ func (repo *UserRepo) CreateUser(ctx context.Context, user *usr_model.User) (*us
return repo.UserEvents.CreateUser(ctx, user, pwPolicyView, orgPolicyView) return repo.UserEvents.CreateUser(ctx, user, pwPolicyView, orgPolicyView)
} }
func (repo *UserRepo) ImportUser(ctx context.Context, user *usr_model.User, passwordChangeRequired bool) (*usr_model.User, error) {
pwPolicy, err := repo.View.PasswordComplexityPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if err != nil && caos_errs.IsNotFound(err) {
pwPolicy, err = repo.View.PasswordComplexityPolicyByAggregateID(repo.SystemDefaults.IamID)
}
if err != nil {
return nil, err
}
pwPolicyView := iam_es_model.PasswordComplexityViewToModel(pwPolicy)
orgPolicy, err := repo.View.OrgIAMPolicyByAggregateID(authz.GetCtxData(ctx).OrgID)
if err != nil && errors.IsNotFound(err) {
orgPolicy, err = repo.View.OrgIAMPolicyByAggregateID(repo.SystemDefaults.IamID)
}
if err != nil {
return nil, err
}
orgPolicyView := iam_es_model.OrgIAMViewToModel(orgPolicy)
return repo.UserEvents.ImportUser(ctx, user, pwPolicyView, orgPolicyView, passwordChangeRequired)
}
func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) { func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) {
policyResourceOwner := authz.GetCtxData(ctx).OrgID policyResourceOwner := authz.GetCtxData(ctx).OrgID
if resourceOwner != "" { if resourceOwner != "" {

View File

@ -86,4 +86,10 @@ type OrgRepository interface {
AddMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error) AddMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error)
ChangeMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error) ChangeMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error)
RemoveMailText(ctx context.Context, mailText *iam_model.MailText) error RemoveMailText(ctx context.Context, mailText *iam_model.MailText) error
GetLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error)
AddLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error)
ChangeLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error)
RemoveLabelPolicy(ctx context.Context) error
} }

View File

@ -10,6 +10,7 @@ import (
type UserRepository interface { type UserRepository interface {
UserByID(ctx context.Context, id string) (*model.UserView, error) UserByID(ctx context.Context, id string) (*model.UserView, error)
CreateUser(ctx context.Context, user *model.User) (*model.User, error) CreateUser(ctx context.Context, user *model.User) (*model.User, error)
ImportUser(ctx context.Context, user *model.User, passwordChangeRequired bool) (*model.User, error)
RegisterUser(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error) RegisterUser(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error)
DeactivateUser(ctx context.Context, id string) (*model.User, error) DeactivateUser(ctx context.Context, id string) (*model.User, error)
ReactivateUser(ctx context.Context, id string) (*model.User, error) ReactivateUser(ctx context.Context, id string) (*model.User, error)

View File

@ -758,6 +758,20 @@ func (es *OrgEventstore) ChangeLabelPolicy(ctx context.Context, policy *iam_mode
return iam_es_model.LabelPolicyToModel(repoOrg.LabelPolicy), nil return iam_es_model.LabelPolicyToModel(repoOrg.LabelPolicy), nil
} }
func (es *OrgEventstore) RemoveLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) error {
if policy == nil || !policy.IsValid() {
return errors.ThrowPreconditionFailed(nil, "EVENT-M9gs", "Errors.Org.LabelPolicy.Invalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(policy.AggregateID))
if err != nil {
return err
}
repoOrg := model.OrgFromModel(org)
addAggregate := LabelPolicyRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg)
return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
}
func (es *OrgEventstore) AddLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) { func (es *OrgEventstore) AddLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) {
if policy == nil || !policy.IsValid() { if policy == nil || !policy.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Sjkl9", "Errors.Org.LoginPolicy.Invalid") return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Sjkl9", "Errors.Org.LoginPolicy.Invalid")

View File

@ -54,7 +54,8 @@ func (l *Login) handleLoginNameCheck(w http.ResponseWriter, r *http.Request) {
return return
} }
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) 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 { if err != nil {
l.renderLogin(w, r, authReq, err) l.renderLogin(w, r, authReq, err)
return return
@ -73,7 +74,7 @@ func (l *Login) renderLogin(w http.ResponseWriter, r *http.Request, authReq *mod
return authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowUsernamePassword return authReq.LoginPolicy != nil && authReq.LoginPolicy.AllowUsernamePassword
}, },
"hasExternalLogin": func() bool { "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) l.renderer.RenderTemplate(w, r, l.renderer.Templates[tmplLogin], data, funcs)

View File

@ -271,6 +271,8 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s
ThemeMode: l.getThemeMode(r), ThemeMode: l.getThemeMode(r),
OrgID: l.getOrgID(authReq), OrgID: l.getOrgID(authReq),
OrgName: l.getOrgName(authReq), OrgName: l.getOrgName(authReq),
PrimaryDomain: l.getOrgPrimaryDomain(authReq),
DisplayLoginNameSuffix: l.isDisplayLoginNameSuffix(authReq),
AuthReqID: getRequestID(authReq, r), AuthReqID: getRequestID(authReq, r),
CSRF: csrf.TemplateField(r), CSRF: csrf.TemplateField(r),
Nonce: http_mw.GetNonce(r), Nonce: http_mw.GetNonce(r),
@ -283,12 +285,14 @@ func (l *Login) getBaseData(r *http.Request, authReq *model.AuthRequest, title s
} }
func (l *Login) getProfileData(authReq *model.AuthRequest) profileData { func (l *Login) getProfileData(authReq *model.AuthRequest) profileData {
var loginName, displayName string var userName, loginName, displayName string
if authReq != nil { if authReq != nil {
userName = authReq.UserName
loginName = authReq.LoginName loginName = authReq.LoginName
displayName = authReq.DisplayName displayName = authReq.DisplayName
} }
return profileData{ return profileData{
UserName: userName,
LoginName: loginName, LoginName: loginName,
DisplayName: displayName, DisplayName: displayName,
} }
@ -329,6 +333,23 @@ func (l *Login) getOrgName(authReq *model.AuthRequest) string {
return authReq.RequestedOrgName return authReq.RequestedOrgName
} }
func (l *Login) getOrgPrimaryDomain(authReq *model.AuthRequest) string {
if authReq == nil {
return ""
}
return authReq.RequestedPrimaryDomain
}
func (l *Login) isDisplayLoginNameSuffix(authReq *model.AuthRequest) bool {
if authReq == nil {
return false
}
if authReq.RequestedOrgID == "" {
return false
}
return authReq.LabelPolicy != nil && !authReq.LabelPolicy.HideLoginNameSuffix
}
func getRequestID(authReq *model.AuthRequest, r *http.Request) string { func getRequestID(authReq *model.AuthRequest, r *http.Request) string {
if authReq != nil { if authReq != nil {
return authReq.ID return authReq.ID
@ -357,6 +378,8 @@ type baseData struct {
ThemeMode string ThemeMode string
OrgID string OrgID string
OrgName string OrgName string
PrimaryDomain string
DisplayLoginNameSuffix bool
AuthReqID string AuthReqID string
CSRF template.HTML CSRF template.HTML
Nonce string Nonce string
@ -380,6 +403,7 @@ type userData struct {
type profileData struct { type profileData struct {
LoginName string LoginName string
UserName string
DisplayName string DisplayName string
} }

View File

@ -6,7 +6,7 @@ Login:
Loginname: Loginname Loginname: Loginname
LoginnamePlaceHolder: username@domain LoginnamePlaceHolder: username@domain
ExternalLogin: Login with an external user. 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: UserSelection:
Title: Select account Title: Select account
@ -16,7 +16,7 @@ UserSelection:
OtherUser: Other User OtherUser: Other User
SessionState0: active SessionState0: active
SessionState1: inactive SessionState1: inactive
MustBeMemberOfOrg: The user must be mermber of the {{.OrgDomain}} organisation. MustBeMemberOfOrg: The user must be mermber of the {{.OrgName}} organisation.
Password: Password:
Title: Password Title: Password

View File

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

View File

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

View File

@ -440,6 +440,10 @@ i {
align-items: center; align-items: center;
border: none; border: none;
outline: none; outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
} }
.lgn-account-selection .lgn-account .left { .lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
@ -1298,6 +1302,10 @@ i {
align-items: center; align-items: center;
border: none; border: none;
outline: none; outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
} }
.lgn-account-selection .lgn-account .left { .lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem; 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; align-items: center;
border: none; border: none;
outline: none; outline: none;
cursor: pointer;
}
.lgn-account-selection .lgn-account:disabled {
cursor: not-allowed;
} }
.lgn-account-selection .lgn-account .left { .lgn-account-selection .lgn-account .left {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
@ -1202,7 +1206,6 @@ a:hover, a:active {
color: inherit; color: inherit;
background: transparent; background: transparent;
box-shadow: inset 0 -1px #303131; box-shadow: inset 0 -1px #303131;
cursor: pointer;
} }
.lgn-account-selection .lgn-account:hover { .lgn-account-selection .lgn-account:hover {
background-color: rgba(255, 255, 255, 0.02); background-color: rgba(255, 255, 255, 0.02);
@ -1522,7 +1525,6 @@ a:hover, a:active {
color: inherit; color: inherit;
background: transparent; background: transparent;
box-shadow: inset 0 -1px #303131; box-shadow: inset 0 -1px #303131;
cursor: pointer;
} }
.lgn-dark-theme .lgn-account-selection .lgn-account:hover { .lgn-dark-theme .lgn-account-selection .lgn-account:hover {
background-color: rgba(255, 255, 255, 0.02); background-color: rgba(255, 255, 255, 0.02);
@ -1836,7 +1838,6 @@ a:hover, a:active {
color: inherit; color: inherit;
background: transparent; background: transparent;
box-shadow: inset 0 -1px #e3e8ee; box-shadow: inset 0 -1px #e3e8ee;
cursor: pointer;
} }
.lgn-light-theme .lgn-account-selection .lgn-account:hover { .lgn-light-theme .lgn-account-selection .lgn-account:hover {
background-color: rgba(0, 0, 0, 0.02); 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 }} {{if hasUsernamePasswordLogin }}
<div class="fields"> <div class="fields">
<label class="lgn-label" for="loginName">{{t "Login.Loginname"}}</label> <label class="lgn-label" for="loginName">{{t "Login.Loginname"}}</label>
<div class="lgn-suffix-wrapper">
<input class="lgn-input lgn-suffix-input" type="text" id="loginName" name="loginName" placeholder="{{t "Login.LoginnamePlaceHolder"}}" <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> 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> </div>
{{end}} {{end}}
@ -53,5 +58,6 @@
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script> <script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script> <script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
<script src="{{ resourceUrl "scripts/input_suffix_offset.js" }}"></script>
{{template "main-bottom" .}} {{template "main-bottom" .}}

View File

@ -23,6 +23,7 @@
<div class="lgn-account-selection"> <div class="lgn-account-selection">
{{ if .Users }} {{ if .Users }}
{{ $displayLoginNameSuffix := and .OrgID (not .DisplayLoginNameSuffix)}}
{{ range $user := .Users }} {{ range $user := .Users }}
{{ $sessionState := (printf "UserSelection.SessionState%v" $user.UserSessionState) }} {{ $sessionState := (printf "UserSelection.SessionState%v" $user.UserSessionState) }}
<button type="submit" name="userID" value="{{$user.UserID}}" class="lgn-account" <button type="submit" name="userID" value="{{$user.UserID}}" class="lgn-account"
@ -34,7 +35,7 @@
</div> </div>
<div class="lgn-names"> <div class="lgn-names">
<p class="lgn-displayname">{{$user.DisplayName}}</p> <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> <p class="lgn-session-state i{{$user.UserSessionState}}">{{t $sessionState}}</p>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>

View File

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

View File

@ -93,9 +93,6 @@ func (u *User) CheckOrgIamPolicy(policy *iam_model.OrgIAMPolicy) error {
if policy == nil { if policy == nil {
return caos_errors.ThrowPreconditionFailed(nil, "MODEL-zSH7j", "Errors.Users.OrgIamPolicyNil") return caos_errors.ThrowPreconditionFailed(nil, "MODEL-zSH7j", "Errors.Users.OrgIamPolicyNil")
} }
if policy.UserLoginMustBeDomain && strings.Contains(u.UserName, "@") {
return caos_errors.ThrowPreconditionFailed(nil, "MODEL-se4sJ", "Errors.User.EmailAsUsernameNotAllowed")
}
if !policy.UserLoginMustBeDomain && u.Profile != nil && u.UserName == "" && u.Email != nil { if !policy.UserLoginMustBeDomain && u.Profile != nil && u.UserName == "" && u.Email != nil {
u.UserName = u.EmailAddress u.UserName = u.EmailAddress
} }

View File

@ -145,7 +145,7 @@ func (es *UserEventstore) prepareCreateMachine(ctx context.Context, user *usr_mo
return machine, createAggregates, err return machine, createAggregates, err
} }
func (es *UserEventstore) prepareCreateHuman(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView, resourceOwner string) (*model.User, []*es_models.Aggregate, error) { func (es *UserEventstore) prepareCreateHuman(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView, changePasswordRequired bool, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
err := user.CheckOrgIAMPolicy(orgIAMPolicy) err := user.CheckOrgIAMPolicy(orgIAMPolicy)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -155,7 +155,7 @@ func (es *UserEventstore) prepareCreateHuman(ctx context.Context, user *usr_mode
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-LoIxJ", "Errors.User.Invalid") return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-LoIxJ", "Errors.User.Invalid")
} }
err = user.HashPasswordIfExisting(pwPolicy, es.PasswordAlg, true) err = user.HashPasswordIfExisting(pwPolicy, es.PasswordAlg, changePasswordRequired)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -177,7 +177,7 @@ func (es *UserEventstore) prepareCreateHuman(ctx context.Context, user *usr_mode
return repoUser, createAggregates, err return repoUser, createAggregates, err
} }
func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView, resourceOwner string) (*model.User, []*es_models.Aggregate, error) { func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView, changePasswordRequired bool, resourceOwner string) (*model.User, []*es_models.Aggregate, error) {
id, err := es.idGenerator.Next() id, err := es.idGenerator.Next()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -185,7 +185,7 @@ func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model
user.AggregateID = id user.AggregateID = id
if user.Human != nil { if user.Human != nil {
return es.prepareCreateHuman(ctx, user, pwPolicy, orgIAMPolicy, resourceOwner) return es.prepareCreateHuman(ctx, user, pwPolicy, orgIAMPolicy, changePasswordRequired, resourceOwner)
} else if user.Machine != nil { } else if user.Machine != nil {
return es.prepareCreateMachine(ctx, user, orgIAMPolicy, resourceOwner) return es.prepareCreateMachine(ctx, user, orgIAMPolicy, resourceOwner)
} }
@ -193,7 +193,22 @@ func (es *UserEventstore) PrepareCreateUser(ctx context.Context, user *usr_model
} }
func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView) (*usr_model.User, error) { func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView) (*usr_model.User, error) {
repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, pwPolicy, orgIAMPolicy, "") repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, pwPolicy, orgIAMPolicy, true, "")
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoUser.AppendEvents, aggregates...)
if err != nil {
return nil, err
}
es.userCache.cacheUser(repoUser)
return model.UserToModel(repoUser), nil
}
func (es *UserEventstore) ImportUser(ctx context.Context, user *usr_model.User, pwPolicy *iam_model.PasswordComplexityPolicyView, orgIAMPolicy *iam_model.OrgIAMPolicyView, changePasswordRequired bool) (*usr_model.User, error) {
repoUser, aggregates, err := es.PrepareCreateUser(ctx, user, pwPolicy, orgIAMPolicy, changePasswordRequired, "")
if err != nil { if err != nil {
return nil, err return nil, err
} }

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

@ -1020,11 +1020,13 @@ message DefaultLabelPolicy {
string secondary_color = 2; string secondary_color = 2;
google.protobuf.Timestamp creation_date = 3; google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4; google.protobuf.Timestamp change_date = 4;
bool hide_login_name_suffix = 5;
} }
message DefaultLabelPolicyUpdate { message DefaultLabelPolicyUpdate {
string primary_color = 1; string primary_color = 1;
string secondary_color = 2; string secondary_color = 2;
bool hide_login_name_suffix = 3;
} }
message DefaultLabelPolicyView { message DefaultLabelPolicyView {
@ -1032,6 +1034,7 @@ message DefaultLabelPolicyView {
string secondary_color = 2; string secondary_color = 2;
google.protobuf.Timestamp creation_date = 3; google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4; google.protobuf.Timestamp change_date = 4;
bool hide_login_name_suffix = 5;
} }
message DefaultMailTemplate { message DefaultMailTemplate {

View File

@ -114,6 +114,17 @@ service ManagementService {
}; };
} }
rpc ImportHuman(ImportHumanRequest) returns (UserResponse) {
option (google.api.http) = {
post: "/users/humans/_import"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "user.write"
};
}
rpc DeactivateUser(UserID) returns (UserResponse) { rpc DeactivateUser(UserID) returns (UserResponse) {
option (google.api.http) = { option (google.api.http) = {
put: "/users/{id}/_deactivate" put: "/users/{id}/_deactivate"
@ -1809,6 +1820,58 @@ service ManagementService {
}; };
} }
rpc GetLabelPolicy(google.protobuf.Empty) returns (LabelPolicyView) {
option (google.api.http) = {
get: "/orgs/me/policies/label"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc GetDefaultLabelPolicy(google.protobuf.Empty) returns (LabelPolicyView) {
option (google.api.http) = {
get: "/orgs/default/policies/label"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc CreateLabelPolicy(LabelPolicyRequest) returns (LabelPolicy) {
option (google.api.http) = {
post: "/orgs/me/policies/label"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc UpdateLabelPolicy(LabelPolicyRequest) returns (LabelPolicy) {
option (google.api.http) = {
put: "/orgs/me/policies/label"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc RemoveLabelPolicy(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/orgs/me/policies/label"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.delete"
};
}
} }
message ZitadelDocs { message ZitadelDocs {
@ -1904,6 +1967,26 @@ message CreateUserRequest {
} }
} }
message ImportHumanRequest {
string user_name = 1 [(validate.rules).string.pattern = "^[^[:space:]]{1,200}$"];
string first_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string last_name = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
string nick_name = 4 [(validate.rules).string = {max_len: 200}];
string preferred_language = 5 [(validate.rules).string = {max_len: 200}];
Gender gender = 6;
string email = 7 [(validate.rules).string = {min_len: 1, max_len: 200, email: true}];
bool is_email_verified = 8;
string phone = 9 [(validate.rules).string = {max_len: 20}];
bool is_phone_verified = 10;
string country = 11 [(validate.rules).string = {max_len: 200}];
string locality = 12 [(validate.rules).string = {max_len: 200}];
string postal_code = 13 [(validate.rules).string = {max_len: 200}];
string region = 14 [(validate.rules).string = {max_len: 200}];
string street_address = 15 [(validate.rules).string = {max_len: 200}];
string password = 16 [(validate.rules).string = {max_len: 72}];
bool password_change_required = 17;
}
message CreateHumanRequest { message CreateHumanRequest {
string first_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; 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 last_name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
@ -3657,3 +3740,28 @@ message MailTextView {
google.protobuf.Timestamp creation_date = 10; google.protobuf.Timestamp creation_date = 10;
google.protobuf.Timestamp change_date = 11; google.protobuf.Timestamp change_date = 11;
} }
message LabelPolicy {
string primary_color = 1;
string secondary_color = 2;
google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4;
bool hide_login_name_suffix = 5;
}
message LabelPolicyRequest {
string primary_color = 1;
string secondary_color = 2;
google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4;
bool hide_login_name_suffix = 5;
}
message LabelPolicyView {
bool default = 1;
string primary_color = 2;
string secondary_color = 3;
google.protobuf.Timestamp creation_date = 4;
google.protobuf.Timestamp change_date = 5;
bool hide_login_name_suffix = 6;
}