zitadel/internal/user/model/user_view.go
Livio Amstutz 71df1bcd0e
fix: improvements for WebAuthN (#1105)
* add missing translations

* add missing passwordless funcs in api

* remove u2f with verification from setup in login
2020-12-15 16:44:16 +01:00

291 lines
7.0 KiB
Go

package model
import (
"time"
"golang.org/x/text/language"
req_model "github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/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
PasswordChangeRequired bool
UsernameChangeRequired bool
PasswordChanged time.Time
FirstName string
LastName string
NickName string
DisplayName 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 req_model.MFALevel
MFAInitSkipped time.Time
InitRequired 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
)
type UserSearchQuery struct {
Key UserSearchKey
Method model.SearchMethod
Value interface{}
}
type UserSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*UserView
Sequence uint64
Timestamp time.Time
}
func (r *UserSearchRequest) EnsureLimit(limit uint64) {
if r.Limit == 0 || r.Limit > limit {
r.Limit = limit
}
}
func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) {
r.Queries = append(r.Queries, &UserSearchQuery{Key: UserSearchKeyResourceOwner, Method: model.SearchMethodEquals, Value: orgID})
}
func (u *UserView) MFATypesSetupPossible(level req_model.MFALevel, policy *iam_model.LoginPolicyView) []req_model.MFAType {
types := make([]req_model.MFAType, 0)
switch level {
default:
fallthrough
case req_model.MFALevelSecondFactor:
if policy.HasSecondFactors() {
for _, mfaType := range policy.SecondFactors {
switch mfaType {
case iam_model.SecondFactorTypeOTP:
if u.OTPState != MFAStateReady {
types = append(types, req_model.MFATypeOTP)
}
case iam_model.SecondFactorTypeU2F:
types = append(types, req_model.MFATypeU2F)
}
}
}
//PLANNED: add sms
}
return types
}
func (u *UserView) MFATypesAllowed(level req_model.MFALevel, policy *iam_model.LoginPolicyView) ([]req_model.MFAType, bool) {
types := make([]req_model.MFAType, 0)
required := true
switch level {
default:
required = policy.ForceMFA
fallthrough
case req_model.MFALevelSecondFactor:
if policy.HasSecondFactors() {
for _, mfaType := range policy.SecondFactors {
switch mfaType {
case iam_model.SecondFactorTypeOTP:
if u.OTPState == MFAStateReady {
types = append(types, req_model.MFATypeOTP)
}
case iam_model.SecondFactorTypeU2F:
if u.IsU2FReady() {
types = append(types, req_model.MFATypeU2F)
}
}
}
}
//PLANNED: add sms
fallthrough
case req_model.MFALevelMultiFactor:
if policy.HasMultiFactors() {
for _, mfaType := range policy.MultiFactors {
switch mfaType {
case iam_model.MultiFactorTypeU2FWithPIN:
if u.IsPasswordlessReady() {
types = append(types, req_model.MFATypeU2FUserVerification)
}
}
}
}
//PLANNED: add token
}
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 req_model.MFALevelSecondFactor:
return policy.HasSecondFactors()
case req_model.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,
}, 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
}