fix: use domain models for v2 eventstore (#1151)

* fix: use domain models for v2 eventstore

* fix: user domain model

* Update internal/api/grpc/admin/login_policy_converter.go

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

* fix: converter

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2021-01-05 09:33:45 +01:00
committed by GitHub
parent 5b84c9b619
commit dc56e298ae
72 changed files with 1029 additions and 610 deletions

View File

@@ -1,5 +1,40 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
caos_errors "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"strings"
"time"
)
type Human struct {
es_models.ObjectRoot
*Password
*Profile
*Email
*Phone
*Address
ExternalIDPs []*ExternalIDP
InitCode *InitUserCode
EmailCode *EmailCode
PhoneCode *PhoneCode
PasswordCode *PasswordCode
OTP *OTP
U2FTokens []*WebAuthNToken
PasswordlessTokens []*WebAuthNToken
U2FLogins []*WebAuthNLogin
PasswordlessLogins []*WebAuthNLogin
}
type InitUserCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
type Gender int32
const (
@@ -14,3 +49,33 @@ const (
func (f Gender) Valid() bool {
return f >= 0 && f < genderCount
}
func (u *Human) IsValid() bool {
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
}
func (u *Human) CheckOrgIAMPolicy(userName string, policy *OrgIAMPolicy) error {
if policy == nil {
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.OrgIamPolicyNil")
}
if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") {
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-se4sJ", "Errors.User.EmailAsUsernameNotAllowed")
}
if !policy.UserLoginMustBeDomain && u.Profile != nil && userName == "" && u.Email != nil {
userName = u.EmailAddress
}
return nil
}
func (u *Human) SetNamesAsDisplayname() {
if u.Profile != nil && u.DisplayName == "" && u.FirstName != "" && u.LastName != "" {
u.DisplayName = u.FirstName + " " + u.LastName
}
}
func (u *Human) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error {
if u.Password != nil {
return u.Password.HashPasswordIfExisting(policy, passwordAlg, onetime)
}
return nil
}

View File

@@ -0,0 +1,13 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type Address struct {
es_models.ObjectRoot
Country string
Locality string
PostalCode string
Region string
StreetAddress string
}

View File

@@ -0,0 +1,25 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"time"
)
type Email struct {
es_models.ObjectRoot
EmailAddress string
IsEmailVerified bool
}
type EmailCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
func (e *Email) IsValid() bool {
return e.EmailAddress != ""
}

View File

@@ -0,0 +1,11 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type ExternalIDP struct {
es_models.ObjectRoot
IDPConfigID string
UserID string
DisplayName string
}

View File

@@ -0,0 +1,15 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type OTP struct {
es_models.ObjectRoot
Secret *crypto.CryptoValue
SecretString string
Url string
State MFAState
}

View File

@@ -0,0 +1,43 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"time"
)
type Password struct {
es_models.ObjectRoot
SecretString string
SecretCrypto *crypto.CryptoValue
ChangeRequired bool
}
type PasswordCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
NotificationType NotificationType
}
func (p *Password) HashPasswordIfExisting(policy *PasswordComplexityPolicy, passwordAlg crypto.HashAlgorithm, onetime bool) error {
if p.SecretString == "" {
return nil
}
if policy == nil {
return caos_errs.ThrowPreconditionFailed(nil, "DOMAIN-s8ifS", "Errors.User.PasswordComplexityPolicy.NotFound")
}
if err := policy.Check(p.SecretString); err != nil {
return err
}
secret, err := crypto.Hash([]byte(p.SecretString), passwordAlg)
if err != nil {
return err
}
p.SecretCrypto = secret
p.ChangeRequired = onetime
return nil
}

View File

@@ -0,0 +1,41 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/ttacon/libphonenumber"
"time"
)
const (
defaultRegion = "CH"
)
type Phone struct {
es_models.ObjectRoot
PhoneNumber string
IsPhoneVerified bool
}
type PhoneCode struct {
es_models.ObjectRoot
Code *crypto.CryptoValue
Expiry time.Duration
}
func (p *Phone) IsValid() bool {
err := p.formatPhone()
return p.PhoneNumber != "" && err == nil
}
func (p *Phone) formatPhone() error {
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
if err != nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
}
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
return nil
}

View File

@@ -0,0 +1,19 @@
package domain
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
"golang.org/x/text/language"
)
type Profile struct {
es_models.ObjectRoot
FirstName string
LastName string
NickName string
DisplayName string
PreferredLanguage language.Tag
Gender Gender
PreferredLoginName string
LoginNames []string
}

View File

@@ -0,0 +1,40 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type WebAuthNToken struct {
es_models.ObjectRoot
WebAuthNTokenID string
CredentialCreationData []byte
State MFAState
Challenge string
AllowedCredentialIDs [][]byte
UserVerification UserVerificationRequirement
KeyID []byte
PublicKey []byte
AttestationType string
AAGUID []byte
SignCount uint32
WebAuthNTokenName string
}
type WebAuthNLogin struct {
es_models.ObjectRoot
CredentialAssertionData []byte
Challenge string
AllowedCredentialIDs [][]byte
UserVerification UserVerificationRequirement
//TODO: Add Auth Request
//*model.AuthRequest
}
type UserVerificationRequirement int32
const (
UserVerificationRequirementUnspecified UserVerificationRequirement = iota
UserVerificationRequirementRequired
UserVerificationRequirementPreferred
UserVerificationRequirementDiscouraged
)

View File

@@ -0,0 +1,16 @@
package domain
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
type IAMMember struct {
es_models.ObjectRoot
UserID string
Roles []string
}
func (i *IAMMember) IsValid() bool {
return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0
}

View File

@@ -1,5 +1,53 @@
package domain
import (
"github.com/caos/zitadel/internal/crypto"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"time"
)
type IDPConfig struct {
es_models.ObjectRoot
IDPConfigID string
Type IDPConfigType
Name string
StylingType IDPConfigStylingType
State IDPConfigState
OIDCConfig *OIDCIDPConfig
}
type IDPConfigView struct {
AggregateID string
IDPConfigID string
Name string
StylingType IDPConfigStylingType
State IDPConfigState
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
IDPProviderType IdentityProviderType
IsOIDC bool
OIDCClientID string
OIDCClientSecret *crypto.CryptoValue
OIDCIssuer string
OIDCScopes []string
OIDCIDPDisplayNameMapping OIDCMappingField
OIDCUsernameMapping OIDCMappingField
}
type OIDCIDPConfig struct {
es_models.ObjectRoot
IDPConfigID string
ClientID string
ClientSecret *crypto.CryptoValue
ClientSecretString string
Issuer string
Scopes []string
IDPDisplayNameMapping OIDCMappingField
UsernameMapping OIDCMappingField
}
type IDPConfigType int32
const (
@@ -32,7 +80,8 @@ func (f IDPConfigState) Valid() bool {
type IDPConfigStylingType int32
const (
IDPConfigStylingTypeGoogle IDPConfigStylingType = iota + 1
IDPConfigStylingTypeUnspecified IDPConfigStylingType = iota
IDPConfigStylingTypeGoogle
idpConfigStylingTypeCount
)

View File

@@ -0,0 +1,14 @@
package domain
import "github.com/caos/zitadel/internal/eventstore/models"
type Machine struct {
models.ObjectRoot
Name string
Description string
}
func (sa *Machine) IsValid() bool {
return sa.Name != ""
}

View File

@@ -0,0 +1,13 @@
package domain
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type LabelPolicy struct {
models.ObjectRoot
Default bool
PrimaryColor string
SecondaryColor string
}

View File

@@ -1,5 +1,27 @@
package domain
import "github.com/caos/zitadel/internal/eventstore/models"
type LoginPolicy struct {
models.ObjectRoot
Default bool
AllowUsernamePassword bool
AllowRegister bool
AllowExternalIdp bool
IDPProviders []*IDPProvider
ForceMFA bool
SecondFactors []SecondFactorType
MultiFactors []MultiFactorType
PasswordlessType PasswordlessType
}
type IDPProvider struct {
models.ObjectRoot
Type IdentityProviderType
IDPConfigID string
}
type PasswordlessType int32
const (

View File

@@ -0,0 +1,12 @@
package domain
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type OrgIAMPolicy struct {
models.ObjectRoot
UserLoginMustBeDomain bool
Default bool
}

View File

@@ -0,0 +1,12 @@
package domain
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type PasswordAgePolicy struct {
models.ObjectRoot
MaxAgeDays uint64
ExpireWarnDays uint64
}

View File

@@ -0,0 +1,56 @@
package domain
import (
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"regexp"
)
var (
hasStringLowerCase = regexp.MustCompile(`[a-z]`).MatchString
hasStringUpperCase = regexp.MustCompile(`[A-Z]`).MatchString
hasNumber = regexp.MustCompile(`[0-9]`).MatchString
hasSymbol = regexp.MustCompile(`[^A-Za-z0-9]`).MatchString
)
type PasswordComplexityPolicy struct {
models.ObjectRoot
MinLength uint64
HasLowercase bool
HasUppercase bool
HasNumber bool
HasSymbol bool
Default bool
}
func (p *PasswordComplexityPolicy) IsValid() error {
if p.MinLength == 0 || p.MinLength > 72 {
return caos_errs.ThrowInvalidArgument(nil, "MODEL-Lsp0e", "Errors.User.PasswordComplexityPolicy.MinLengthNotAllowed")
}
return nil
}
func (p *PasswordComplexityPolicy) Check(password string) error {
if p.MinLength != 0 && uint64(len(password)) < p.MinLength {
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength")
}
if p.HasLowercase && !hasStringLowerCase(password) {
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-co3Xw", "Errors.User.PasswordComplexityPolicy.HasLower")
}
if p.HasUppercase && !hasStringUpperCase(password) {
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-VoaRj", "Errors.User.PasswordComplexityPolicy.HasUpper")
}
if p.HasNumber && !hasNumber(password) {
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-ZBv4H", "Errors.User.PasswordComplexityPolicy.HasNumber")
}
if p.HasSymbol && !hasSymbol(password) {
return caos_errs.ThrowInvalidArgument(nil, "DOMAIN-ZDLwA", "Errors.User.PasswordComplexityPolicy.HasSymbol")
}
return nil
}

View File

@@ -0,0 +1,12 @@
package domain
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type PasswordLockoutPolicy struct {
models.ObjectRoot
MaxAttempts uint64
ShowLockOutFailures bool
}

View File

@@ -1,5 +1,16 @@
package domain
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type User struct {
es_models.ObjectRoot
State UserState
UserName string
*Human
*Machine
}
type UserState int32
const (
@@ -17,3 +28,13 @@ const (
func (f UserState) Valid() bool {
return f >= 0 && f < userStateCount
}
func (u *User) IsValid() bool {
if u.Human == nil && u.Machine == nil || u.UserName == "" {
return false
}
if u.Human != nil {
return u.Human.IsValid()
}
return u.Machine.IsValid()
}