mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 11:34:26 +00:00
fed15574f6
This PR adds an option to the LoginPolicy to "Force MFA for local users", so that users authenticated through an IDP must not configure (and verify) an MFA.
309 lines
7.3 KiB
Go
309 lines
7.3 KiB
Go
package model
|
|
|
|
import (
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
iam_model "github.com/zitadel/zitadel/internal/iam/model"
|
|
)
|
|
|
|
type UserView struct {
|
|
ID string
|
|
UserName string
|
|
CreationDate time.Time
|
|
ChangeDate time.Time
|
|
State UserState
|
|
Sequence uint64
|
|
ResourceOwner string
|
|
LastLogin time.Time
|
|
PreferredLoginName string
|
|
LoginNames []string
|
|
*MachineView
|
|
*HumanView
|
|
}
|
|
|
|
type HumanView struct {
|
|
PasswordSet bool
|
|
PasswordInitRequired bool
|
|
PasswordChangeRequired bool
|
|
UsernameChangeRequired bool
|
|
PasswordChanged time.Time
|
|
FirstName string
|
|
LastName string
|
|
NickName string
|
|
DisplayName string
|
|
AvatarKey string
|
|
PreferredLanguage string
|
|
Gender Gender
|
|
Email string
|
|
IsEmailVerified bool
|
|
Phone string
|
|
IsPhoneVerified bool
|
|
Country string
|
|
Locality string
|
|
PostalCode string
|
|
Region string
|
|
StreetAddress string
|
|
OTPState MFAState
|
|
U2FTokens []*WebAuthNView
|
|
PasswordlessTokens []*WebAuthNView
|
|
MFAMaxSetUp domain.MFALevel
|
|
MFAInitSkipped time.Time
|
|
InitRequired bool
|
|
PasswordlessInitRequired bool
|
|
}
|
|
|
|
type WebAuthNView struct {
|
|
TokenID string
|
|
Name string
|
|
State MFAState
|
|
}
|
|
|
|
type MachineView struct {
|
|
LastKeyAdded time.Time
|
|
Name string
|
|
Description string
|
|
}
|
|
|
|
type UserSearchRequest struct {
|
|
Offset uint64
|
|
Limit uint64
|
|
SortingColumn UserSearchKey
|
|
Asc bool
|
|
Queries []*UserSearchQuery
|
|
}
|
|
|
|
type UserSearchKey int32
|
|
|
|
const (
|
|
UserSearchKeyUnspecified UserSearchKey = iota
|
|
UserSearchKeyUserID
|
|
UserSearchKeyUserName
|
|
UserSearchKeyFirstName
|
|
UserSearchKeyLastName
|
|
UserSearchKeyNickName
|
|
UserSearchKeyDisplayName
|
|
UserSearchKeyEmail
|
|
UserSearchKeyState
|
|
UserSearchKeyResourceOwner
|
|
UserSearchKeyLoginNames
|
|
UserSearchKeyType
|
|
UserSearchKeyPreferredLoginName
|
|
UserSearchKeyInstanceID
|
|
UserSearchOwnerRemoved
|
|
)
|
|
|
|
type UserSearchQuery struct {
|
|
Key UserSearchKey
|
|
Method domain.SearchMethod
|
|
Value interface{}
|
|
}
|
|
|
|
type UserSearchResponse struct {
|
|
Offset uint64
|
|
Limit uint64
|
|
TotalResult uint64
|
|
Result []*UserView
|
|
Sequence uint64
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type UserState int32
|
|
|
|
const (
|
|
UserStateUnspecified UserState = iota
|
|
UserStateActive
|
|
UserStateInactive
|
|
UserStateDeleted
|
|
UserStateLocked
|
|
UserStateSuspend
|
|
UserStateInitial
|
|
)
|
|
|
|
type Gender int32
|
|
|
|
const (
|
|
GenderUnspecified Gender = iota
|
|
GenderFemale
|
|
GenderMale
|
|
GenderDiverse
|
|
)
|
|
|
|
func (r *UserSearchRequest) EnsureLimit(limit uint64) error {
|
|
if r.Limit > limit {
|
|
return errors.ThrowInvalidArgument(nil, "SEARCH-zz62F", "Errors.Limit.ExceedsDefault")
|
|
}
|
|
if r.Limit == 0 {
|
|
r.Limit = limit
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) {
|
|
r.Queries = append(r.Queries, &UserSearchQuery{Key: UserSearchKeyResourceOwner, Method: domain.SearchMethodEquals, Value: orgID})
|
|
}
|
|
|
|
func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.LoginPolicy) []domain.MFAType {
|
|
types := make([]domain.MFAType, 0)
|
|
switch level {
|
|
default:
|
|
fallthrough
|
|
case domain.MFALevelSecondFactor:
|
|
if policy.HasSecondFactors() {
|
|
for _, mfaType := range policy.SecondFactors {
|
|
switch mfaType {
|
|
case domain.SecondFactorTypeOTP:
|
|
if u.OTPState != MFAStateReady {
|
|
types = append(types, domain.MFATypeOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
types = append(types, domain.MFATypeU2F)
|
|
}
|
|
}
|
|
}
|
|
//PLANNED: add sms
|
|
}
|
|
return types
|
|
}
|
|
|
|
func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy, isInternalAuthentication bool) ([]domain.MFAType, bool) {
|
|
types := make([]domain.MFAType, 0)
|
|
required := true
|
|
switch level {
|
|
default:
|
|
required = domain.RequiresMFA(policy.ForceMFA, policy.ForceMFALocalOnly, isInternalAuthentication)
|
|
fallthrough
|
|
case domain.MFALevelSecondFactor:
|
|
if policy.HasSecondFactors() {
|
|
for _, mfaType := range policy.SecondFactors {
|
|
switch mfaType {
|
|
case domain.SecondFactorTypeOTP:
|
|
if u.OTPState == MFAStateReady {
|
|
types = append(types, domain.MFATypeOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
if u.IsU2FReady() {
|
|
types = append(types, domain.MFATypeU2F)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//PLANNED: add sms
|
|
}
|
|
return types, required
|
|
}
|
|
|
|
func (u *UserView) IsU2FReady() bool {
|
|
for _, token := range u.U2FTokens {
|
|
if token.State == MFAStateReady {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (u *UserView) IsPasswordlessReady() bool {
|
|
for _, token := range u.PasswordlessTokens {
|
|
if token.State == MFAStateReady {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (u *UserView) HasRequiredOrgMFALevel(policy *iam_model.LoginPolicyView) bool {
|
|
if !policy.ForceMFA {
|
|
return true
|
|
}
|
|
switch u.MFAMaxSetUp {
|
|
case domain.MFALevelSecondFactor:
|
|
return policy.HasSecondFactors()
|
|
case domain.MFALevelMultiFactor:
|
|
return policy.HasMultiFactors()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (u *UserView) GetProfile() (*Profile, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-WLTce", "Errors.User.NotHuman")
|
|
}
|
|
return &Profile{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
FirstName: u.FirstName,
|
|
LastName: u.LastName,
|
|
NickName: u.NickName,
|
|
DisplayName: u.DisplayName,
|
|
PreferredLanguage: language.Make(u.PreferredLanguage),
|
|
Gender: u.Gender,
|
|
PreferredLoginName: u.PreferredLoginName,
|
|
LoginNames: u.LoginNames,
|
|
AvatarKey: u.AvatarKey,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetPhone() (*Phone, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-him4a", "Errors.User.NotHuman")
|
|
}
|
|
return &Phone{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
PhoneNumber: u.Phone,
|
|
IsPhoneVerified: u.IsPhoneVerified,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetEmail() (*Email, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-PWd6K", "Errors.User.NotHuman")
|
|
}
|
|
return &Email{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
EmailAddress: u.Email,
|
|
IsEmailVerified: u.IsEmailVerified,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetAddress() (*Address, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-DN61m", "Errors.User.NotHuman")
|
|
}
|
|
return &Address{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
Country: u.Country,
|
|
Locality: u.Locality,
|
|
PostalCode: u.PostalCode,
|
|
Region: u.Region,
|
|
StreetAddress: u.StreetAddress,
|
|
}, nil
|
|
}
|