mirror of
https://github.com/zitadel/zitadel.git
synced 2025-06-01 11:08:19 +00:00
feat: user commands (#75)
* feat: eventstore repository * fix: remove gorm * version * feat: pkg * feat: add some files for project * feat: eventstore without eventstore-lib * rename files * gnueg * fix: key json * fix: add object * fix: change imports * fix: internal models * fix: some imports * fix: global model * feat: add global view functions * fix: add some functions on repo * feat(eventstore): sdk * fix(eventstore): search query * fix(eventstore): rename app to eventstore * delete empty test * remove unused func * merge master * fix(eventstore): tests * fix(models): delete unused struct * fix: some funcitons * feat(eventstore): implemented push events * fix: move project eventstore to project package * fix: change project eventstore funcs * feat(eventstore): overwrite context data * fix: change project eventstore * fix: add project repo to mgmt server * feat(types): SQL-config * fix: commented code * feat(eventstore): options to overwrite editor * feat: auth interceptor and cockroach migrations * fix: migrations * fix: fix filter * fix: not found on getbyid * fix: use global sql config * fix: add sequence * fix: add some tests * fix(eventstore): nullable sequence * fix: add some tests * merge * fix: add some tests * fix(migrations): correct statements for sequence * fix: add some tests * fix: add some tests * fix: changes from mr * fix: changes from mr * fix: add some tests * Update internal/eventstore/models/field.go Co-Authored-By: livio-a <livio.a@gmail.com> * fix(eventstore): code quality * fix: add types to aggregate/Event-types * fix: try tests * fix(eventstore): rename modifier* to editor* * fix(eventstore): delete editor_org * fix(migrations): remove editor_org field, rename modifier_* to editor_* * fix: query tests * fix: use prepare funcs * fix: go mod * fix: generate files * fix(eventstore): tests * fix(eventstore): rename modifier to editor * fix(migrations): add cluster migration, fix(migrations): fix typo of host in clean clsuter * fix(eventstore): move health * fix(eventstore): AggregateTypeFilter aggregateType as param * code quality * fix: go tests * feat: add member funcs * feat: add member model * feat: add member events * feat: add member repo model * fix: better error func testing * fix: project member funcs * fix: add tests * fix: add tests * feat: implement member requests * fix: merge master * fix: merge master * fix: read existing in project repo * fix: fix tests * feat: add internal cache * feat: add cache mock * fix: return values of cache mock * feat: add project role * fix: add cache config * fix: add role to eventstore * fix: use eventstore sdk * fix: use eventstore sdk * fix: add project role grpc requests * fix: fix getby id * fix: changes for mr * fix: change value to interface * feat: add app event creations * fix: searchmethods * Update internal/project/model/project_member.go Co-Authored-By: Silvan <silvan.reusser@gmail.com> * fix: use get project func * fix: append events * fix: check if value is string on equal ignore case * fix: add changes test * fix: add go mod * fix: add some tests * fix: return err not nil * fix: return err not nil * fix: add aggregate funcs and tests * fix: add oidc aggregate funcs and tests * fix: add oidc * fix: add some tests * fix: tests * fix: oidc validation * fix: generate client secret * fix: generate client id * fix: test change app * fix: deactivate/reactivate application * fix: change oidc config * fix: change oidc config secret * fix: implement grpc app funcs * fix: add application requests * fix: converter * fix: converter * fix: converter and generate clientid * fix: tests * feat: project grant aggregate * feat: project grant * fix: project grant check if role existing * fix: project grant requests * fix: project grant fixes * fix: project grant member model * fix: project grant member aggregate * fix: project grant member eventstore * fix: project grant member requests * feat: user model * feat: user command side * user command side * profile requests * local config with gopass and more * Update internal/user/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/user_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/eventstore_mock_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * changes from mr review * save files into basedir * changes from mr review * changes from mr review * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * changes requested on mr * fix generate codes * fix return if no events * password code * Update internal/user/repository/eventsourcing/model/password.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * requests of mr * check email Co-authored-by: adlerhurst <silvan.reusser@gmail.com> Co-authored-by: livio-a <livio.a@gmail.com>
This commit is contained in:
parent
380e4d0643
commit
49d86fdabb
1
cmd/zitadel/.gitignore
vendored
Normal file
1
cmd/zitadel/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
local_*
|
22
cmd/zitadel/caos_local.sh
Normal file
22
cmd/zitadel/caos_local.sh
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
BASEDIR=$(dirname "$0")
|
||||||
|
|
||||||
|
# Tracing
|
||||||
|
gopass citadel-secrets/citadel/developer/default/citadel-svc-account-eventstore-local | base64 -D > "$BASEDIR/local_svc-account-tracing.json"
|
||||||
|
export GOOGLE_APPLICATION_CREDENTIALS="$BASEDIR/local_svc-account-tracing.json"
|
||||||
|
|
||||||
|
export ZITADEL_TRACING_PROJECT_ID=caos-citadel-test
|
||||||
|
export ZITADEL_TRACING_FRACTION=0.1
|
||||||
|
|
||||||
|
# Log
|
||||||
|
export ZITADEL_LOG_LEVEL=debug
|
||||||
|
|
||||||
|
# Cockroach
|
||||||
|
export ZITADEL_EVENTSTORE_HOST=localhost
|
||||||
|
export ZITADEL_EVENTSTORE_PORT=26257
|
||||||
|
|
||||||
|
# Keys
|
||||||
|
gopass citadel-secrets/citadel/developer/default/keys.yaml > "$BASEDIR/local_keys.yaml"
|
||||||
|
export ZITADEL_KEY_PATH="$BASEDIR/local_keys.yaml"
|
||||||
|
|
||||||
|
export ZITADEL_USER_VERIFICATION_KEY=UserVerificationKey_1
|
||||||
|
export ZITADEL_OTP_VERIFICATION_KEY=OTPVerificationKey_1
|
@ -31,8 +31,8 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var configPaths config.ArrayFlags
|
configPaths := config.NewArrayFlags("authz.yaml", "startup.yaml", "system-defaults.yaml")
|
||||||
flag.Var(&configPaths, "config-files", "path to the config files")
|
flag.Var(configPaths, "config-files", "paths to the config files")
|
||||||
managementEnabled := flag.Bool("management", true, "enable management api")
|
managementEnabled := flag.Bool("management", true, "enable management api")
|
||||||
authEnabled := flag.Bool("auth", true, "enable auth api")
|
authEnabled := flag.Bool("auth", true, "enable auth api")
|
||||||
loginEnabled := flag.Bool("login", true, "enable login ui")
|
loginEnabled := flag.Bool("login", true, "enable login ui")
|
||||||
@ -41,7 +41,7 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
conf := new(Config)
|
conf := new(Config)
|
||||||
err := config.Read(conf, configPaths...)
|
err := config.Read(conf, configPaths.Values()...)
|
||||||
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
|
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
Tracing:
|
Tracing:
|
||||||
Type: google
|
Type: google
|
||||||
Config:
|
Config:
|
||||||
ProjectID: $TRACING_PROJECT_ID
|
ProjectID: $ZITADEL_TRACING_PROJECT_ID
|
||||||
MetricPrefix: ZITADEL-V1
|
MetricPrefix: ZITADEL-V1
|
||||||
Fraction: 1
|
Fraction: $ZITADEL_TRACING_FRACTION
|
||||||
|
|
||||||
Log:
|
Log:
|
||||||
Level: debug
|
Level: $ZITADEL_LOG_LEVEL
|
||||||
Formatter:
|
Formatter:
|
||||||
Format: text
|
Format: text
|
||||||
|
|
||||||
@ -22,8 +22,8 @@ Mgmt:
|
|||||||
ServiceName: 'ManagementAPI'
|
ServiceName: 'ManagementAPI'
|
||||||
Repository:
|
Repository:
|
||||||
SQL:
|
SQL:
|
||||||
Host: $CR_HOST
|
Host: $ZITADEL_EVENTSTORE_HOST
|
||||||
Port: $CR_PORT
|
Port: $ZITADEL_EVENTSTORE_PORT
|
||||||
User: 'management'
|
User: 'management'
|
||||||
Database: 'management'
|
Database: 'management'
|
||||||
SSLmode: disable
|
SSLmode: disable
|
||||||
@ -32,7 +32,6 @@ Mgmt:
|
|||||||
Config:
|
Config:
|
||||||
MaxCacheSizeInByte: 10485760 #10mb
|
MaxCacheSizeInByte: 10485760 #10mb
|
||||||
|
|
||||||
|
|
||||||
Auth:
|
Auth:
|
||||||
API:
|
API:
|
||||||
GRPC:
|
GRPC:
|
||||||
|
@ -1,8 +1,44 @@
|
|||||||
SecretGenerators:
|
SystemDefaults:
|
||||||
PasswordSaltCost: 14
|
UserVerificationKey:
|
||||||
ClientSecretGenerator:
|
EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY
|
||||||
Length: 64
|
SecretGenerators:
|
||||||
IncludeLowerLetters: true
|
PasswordSaltCost: 14
|
||||||
IncludeUpperLetters: true
|
ClientSecretGenerator:
|
||||||
IncludeDigits: true
|
Length: 64
|
||||||
IncludeSymbols: true
|
IncludeLowerLetters: true
|
||||||
|
IncludeUpperLetters: true
|
||||||
|
IncludeDigits: true
|
||||||
|
IncludeSymbols: true
|
||||||
|
InitializeUserCode:
|
||||||
|
Length: 6
|
||||||
|
Expiry: '72h'
|
||||||
|
IncludeLowerLetters: false
|
||||||
|
IncludeUpperLetters: true
|
||||||
|
IncludeDigits: true
|
||||||
|
IncludeSymbols: false
|
||||||
|
EmailVerificationCode:
|
||||||
|
Length: 6
|
||||||
|
Expiry: '1h'
|
||||||
|
IncludeLowerLetters: false
|
||||||
|
IncludeUpperLetters: true
|
||||||
|
IncludeDigits: true
|
||||||
|
IncludeSymbols: false
|
||||||
|
PhoneVerificationCode:
|
||||||
|
Length: 6
|
||||||
|
Expiry: '1h'
|
||||||
|
IncludeLowerLetters: false
|
||||||
|
IncludeUpperLetters: true
|
||||||
|
IncludeDigits: true
|
||||||
|
IncludeSymbols: false
|
||||||
|
PasswordVerificationCode:
|
||||||
|
Length: 6
|
||||||
|
Expiry: '1h'
|
||||||
|
IncludeLowerLetters: false
|
||||||
|
IncludeUpperLetters: true
|
||||||
|
IncludeDigits: true
|
||||||
|
IncludeSymbols: false
|
||||||
|
Multifactors:
|
||||||
|
OTP:
|
||||||
|
Issuer: 'Zitadel'
|
||||||
|
VerificationKey:
|
||||||
|
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
|
@ -31,3 +31,4 @@ cockroachdb/cockroach:v19.2.2 start --insecure
|
|||||||
|
|
||||||
#### Should show eventstore, management, admin, auth
|
#### Should show eventstore, management, admin, auth
|
||||||
`show databases;`
|
`show databases;`
|
||||||
|
|
||||||
|
1
go.mod
1
go.mod
@ -34,6 +34,7 @@ require (
|
|||||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.0.3
|
github.com/nicksnyder/go-i18n/v2 v2.0.3
|
||||||
|
github.com/pquerna/otp v1.2.0
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/sirupsen/logrus v1.5.0 // indirect
|
github.com/sirupsen/logrus v1.5.0 // indirect
|
||||||
github.com/sony/sonyflake v1.0.0
|
github.com/sony/sonyflake v1.0.0
|
||||||
|
4
go.sum
4
go.sum
@ -54,6 +54,8 @@ github.com/aws/aws-sdk-go v1.30.4 h1:dpQgypC3rld2Uuz+/2u+0nbfmmyEWxau6v1hdAlvoc8
|
|||||||
github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.30.4/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
github.com/aws/aws-sdk-go v1.30.16 h1:1eabOZiVGl+XB02/VG9NpR+3fngaNc74pgb5RmyzgLY=
|
github.com/aws/aws-sdk-go v1.30.16 h1:1eabOZiVGl+XB02/VG9NpR+3fngaNc74pgb5RmyzgLY=
|
||||||
github.com/aws/aws-sdk-go v1.30.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.30.16/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
||||||
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
||||||
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
||||||
@ -262,6 +264,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
|
||||||
|
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
@ -9,13 +9,29 @@ var _ flag.Value = (*ArrayFlags)(nil)
|
|||||||
|
|
||||||
//ArrayFlags implements the flag/Value interface
|
//ArrayFlags implements the flag/Value interface
|
||||||
//allowing to set multiple string flags with the same name
|
//allowing to set multiple string flags with the same name
|
||||||
type ArrayFlags []string
|
type ArrayFlags struct {
|
||||||
|
defaultValues []string
|
||||||
|
values []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArrayFlags(defaults ...string) *ArrayFlags {
|
||||||
|
return &ArrayFlags{
|
||||||
|
defaultValues: defaults,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ArrayFlags) Values() []string {
|
||||||
|
if len(i.values) == 0 {
|
||||||
|
return i.defaultValues
|
||||||
|
}
|
||||||
|
return i.values
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ArrayFlags) String() string {
|
func (i *ArrayFlags) String() string {
|
||||||
return strings.Join(*i, ";")
|
return strings.Join(i.Values(), ";")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ArrayFlags) Set(value string) error {
|
func (i *ArrayFlags) Set(value string) error {
|
||||||
*i = append(*i, value)
|
i.values = append(i.values, value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,25 @@ package systemdefaults
|
|||||||
import "github.com/caos/zitadel/internal/crypto"
|
import "github.com/caos/zitadel/internal/crypto"
|
||||||
|
|
||||||
type SystemDefaults struct {
|
type SystemDefaults struct {
|
||||||
SecretGenerator SecretGenerator
|
SecretGenerators SecretGenerators
|
||||||
|
UserVerificationKey *crypto.KeyConfig
|
||||||
|
Multifactors MultifactorConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type SecretGenerator struct {
|
type SecretGenerators struct {
|
||||||
PasswordSaltCost int
|
PasswordSaltCost int
|
||||||
ClientSecretGenerator crypto.GeneratorConfig
|
ClientSecretGenerator crypto.GeneratorConfig
|
||||||
|
InitializeUserCode crypto.GeneratorConfig
|
||||||
|
EmailVerificationCode crypto.GeneratorConfig
|
||||||
|
PhoneVerificationCode crypto.GeneratorConfig
|
||||||
|
PasswordVerificationCode crypto.GeneratorConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type MultifactorConfig struct {
|
||||||
|
OTP OTPConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type OTPConfig struct {
|
||||||
|
Issuer string
|
||||||
|
VerificationKey *crypto.KeyConfig
|
||||||
}
|
}
|
||||||
|
59
internal/crypto/code_mocker.go
Normal file
59
internal/crypto/code_mocker.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateMockEncryptionAlg(ctrl *gomock.Controller) EncryptionAlgorithm {
|
||||||
|
mCrypto := NewMockEncryptionAlgorithm(ctrl)
|
||||||
|
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
|
||||||
|
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
|
||||||
|
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
|
||||||
|
mCrypto.EXPECT().Encrypt(gomock.Any()).DoAndReturn(
|
||||||
|
func(code []byte) ([]byte, error) {
|
||||||
|
return code, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||||
|
func(code []byte, keyID string) (string, error) {
|
||||||
|
if keyID != "id" {
|
||||||
|
return "", errors.ThrowInternal(nil, "id", "invalid key id")
|
||||||
|
}
|
||||||
|
return string(code), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return mCrypto
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMockHashAlg(t *testing.T) HashAlgorithm {
|
||||||
|
mCrypto := NewMockHashAlgorithm(gomock.NewController(t))
|
||||||
|
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
|
||||||
|
mCrypto.EXPECT().Hash(gomock.Any()).DoAndReturn(
|
||||||
|
func(code []byte) ([]byte, error) {
|
||||||
|
return code, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||||
|
func(hashed, comparer []byte) error {
|
||||||
|
if string(hashed) != string(comparer) {
|
||||||
|
return errors.ThrowInternal(nil, "id", "invalid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return mCrypto
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMockCrypto(t *testing.T) Crypto {
|
||||||
|
mCrypto := NewMockCrypto(gomock.NewController(t))
|
||||||
|
mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto")
|
||||||
|
return mCrypto
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMockGenerator(t *testing.T, crypto Crypto) Generator {
|
||||||
|
mGenerator := NewMockGenerator(gomock.NewController(t))
|
||||||
|
mGenerator.EXPECT().Alg().AnyTimes().Return(crypto)
|
||||||
|
return mGenerator
|
||||||
|
}
|
@ -5,62 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func createMockEncryptionAlg(t *testing.T) EncryptionAlgorithm {
|
|
||||||
mCrypto := NewMockEncryptionAlgorithm(gomock.NewController(t))
|
|
||||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
|
|
||||||
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
|
|
||||||
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
|
|
||||||
mCrypto.EXPECT().Encrypt(gomock.Any()).DoAndReturn(
|
|
||||||
func(code []byte) ([]byte, error) {
|
|
||||||
return code, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
||||||
func(code []byte, keyID string) (string, error) {
|
|
||||||
if keyID != "id" {
|
|
||||||
return "", errors.ThrowInternal(nil, "id", "invalid key id")
|
|
||||||
}
|
|
||||||
return string(code), nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return mCrypto
|
|
||||||
}
|
|
||||||
|
|
||||||
func createMockHashAlg(t *testing.T) HashAlgorithm {
|
|
||||||
mCrypto := NewMockHashAlgorithm(gomock.NewController(t))
|
|
||||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("hash")
|
|
||||||
mCrypto.EXPECT().Hash(gomock.Any()).DoAndReturn(
|
|
||||||
func(code []byte) ([]byte, error) {
|
|
||||||
return code, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
mCrypto.EXPECT().CompareHash(gomock.Any(), gomock.Any()).DoAndReturn(
|
|
||||||
func(hashed, comparer []byte) error {
|
|
||||||
if string(hashed) != string(comparer) {
|
|
||||||
return errors.ThrowInternal(nil, "id", "invalid")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return mCrypto
|
|
||||||
}
|
|
||||||
|
|
||||||
func createMockCrypto(t *testing.T) Crypto {
|
|
||||||
mCrypto := NewMockCrypto(gomock.NewController(t))
|
|
||||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("crypto")
|
|
||||||
return mCrypto
|
|
||||||
}
|
|
||||||
|
|
||||||
func createMockGenerator(t *testing.T, crypto Crypto) Generator {
|
|
||||||
mGenerator := NewMockGenerator(gomock.NewController(t))
|
|
||||||
mGenerator.EXPECT().Alg().AnyTimes().Return(crypto)
|
|
||||||
return mGenerator
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsCodeExpired(t *testing.T) {
|
func TestIsCodeExpired(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
creationDate time.Time
|
creationDate time.Time
|
||||||
@ -152,7 +98,7 @@ func TestVerifyCode(t *testing.T) {
|
|||||||
Crypted: []byte("code"),
|
Crypted: []byte("code"),
|
||||||
},
|
},
|
||||||
verificationCode: "code",
|
verificationCode: "code",
|
||||||
g: createMockGenerator(t, createMockEncryptionAlg(t)),
|
g: createMockGenerator(t, CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
@ -197,7 +143,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
cryptoCode: nil,
|
cryptoCode: nil,
|
||||||
verificationCode: "",
|
verificationCode: "",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -209,7 +155,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
Crypted: nil,
|
Crypted: nil,
|
||||||
},
|
},
|
||||||
verificationCode: "",
|
verificationCode: "",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -222,7 +168,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
Crypted: nil,
|
Crypted: nil,
|
||||||
},
|
},
|
||||||
verificationCode: "",
|
verificationCode: "",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -235,7 +181,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
Crypted: nil,
|
Crypted: nil,
|
||||||
},
|
},
|
||||||
verificationCode: "wrong",
|
verificationCode: "wrong",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -249,7 +195,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
Crypted: []byte("code"),
|
Crypted: []byte("code"),
|
||||||
},
|
},
|
||||||
verificationCode: "wrong",
|
verificationCode: "wrong",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
@ -263,7 +209,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
|||||||
Crypted: []byte("code"),
|
Crypted: []byte("code"),
|
||||||
},
|
},
|
||||||
verificationCode: "code",
|
verificationCode: "code",
|
||||||
alg: createMockEncryptionAlg(t),
|
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -23,3 +23,6 @@ func (o *ObjectRoot) AppendEvent(event *Event) {
|
|||||||
|
|
||||||
o.Sequence = event.Sequence
|
o.Sequence = event.Sequence
|
||||||
}
|
}
|
||||||
|
func (o *ObjectRoot) IsZero() bool {
|
||||||
|
return o.AggregateID == ""
|
||||||
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
es_int "github.com/caos/zitadel/internal/eventstore"
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||||
es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
es_usr "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
|
es_grant "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -17,6 +19,8 @@ type Config struct {
|
|||||||
type EsRepository struct {
|
type EsRepository struct {
|
||||||
//spooler *es_spooler.Spooler
|
//spooler *es_spooler.Spooler
|
||||||
ProjectRepo
|
ProjectRepo
|
||||||
|
UserRepo
|
||||||
|
UserGrantRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error) {
|
func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error) {
|
||||||
@ -42,9 +46,24 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
user, err := es_usr.StartUser(es_usr.UserConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
}, systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
usergrant, err := es_grant.StartUserGrant(es_grant.UserGrantConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &EsRepository{
|
return &EsRepository{
|
||||||
ProjectRepo{project},
|
ProjectRepo{project},
|
||||||
|
UserRepo{user},
|
||||||
|
UserGrantRepo{usergrant},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
internal/management/repository/eventsourcing/user.go
Normal file
87
internal/management/repository/eventsourcing/user.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepo struct {
|
||||||
|
UserEvents *usr_event.UserEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) UserByID(ctx context.Context, id string) (project *usr_model.User, err error) {
|
||||||
|
return repo.UserEvents.UserByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.CreateUser(ctx, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.RegisterUser(ctx, user, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) DeactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.DeactivateUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ReactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.ReactivateUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) LockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.LockUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) UnlockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
return repo.UserEvents.UnlockUser(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
|
||||||
|
return repo.UserEvents.SetOneTimePassword(ctx, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) RequestSetPassword(ctx context.Context, id string, notifyType usr_model.NotificationType) error {
|
||||||
|
return repo.UserEvents.RequestSetPassword(ctx, id, notifyType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
||||||
|
return repo.UserEvents.ProfileByID(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
|
||||||
|
return repo.UserEvents.ChangeProfile(ctx, profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) {
|
||||||
|
return repo.UserEvents.EmailByID(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ChangeEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) {
|
||||||
|
return repo.UserEvents.ChangeEmail(ctx, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) CreateEmailVerificationCode(ctx context.Context, userID string) error {
|
||||||
|
return repo.UserEvents.CreateEmailVerificationCode(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) {
|
||||||
|
return repo.UserEvents.PhoneByID(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ChangePhone(ctx context.Context, email *usr_model.Phone) (*usr_model.Phone, error) {
|
||||||
|
return repo.UserEvents.ChangePhone(ctx, email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) CreatePhoneVerificationCode(ctx context.Context, userID string) error {
|
||||||
|
return repo.UserEvents.CreatePhoneVerificationCode(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) {
|
||||||
|
return repo.UserEvents.AddressByID(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
|
||||||
|
return repo.UserEvents.ChangeAddress(ctx, address)
|
||||||
|
}
|
35
internal/management/repository/eventsourcing/user_grant.go
Normal file
35
internal/management/repository/eventsourcing/user_grant.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
grant_event "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepo struct {
|
||||||
|
UserGrantEvents *grant_event.UserGrantEventStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) UserGrantByID(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
|
||||||
|
return repo.UserGrantEvents.UserGrantByID(ctx, grantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) AddUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
|
||||||
|
return repo.UserGrantEvents.AddUserGrant(ctx, grant)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) ChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
|
||||||
|
return repo.UserGrantEvents.ChangeUserGrant(ctx, grant)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) DeactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
|
||||||
|
return repo.UserGrantEvents.DeactivateUserGrant(ctx, grantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) ReactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
|
||||||
|
return repo.UserGrantEvents.ReactivateUserGrant(ctx, grantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) RemoveUserGrant(ctx context.Context, grantID string) error {
|
||||||
|
return repo.UserGrantEvents.RemoveUserGrant(ctx, grantID)
|
||||||
|
}
|
@ -3,4 +3,6 @@ package repository
|
|||||||
type Repository interface {
|
type Repository interface {
|
||||||
Health() error
|
Health() error
|
||||||
ProjectRepository
|
ProjectRepository
|
||||||
|
UserRepository
|
||||||
|
UserGrantRepository
|
||||||
}
|
}
|
||||||
|
33
internal/management/repository/user.go
Normal file
33
internal/management/repository/user.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRepository interface {
|
||||||
|
UserByID(ctx context.Context, id string) (*model.User, error)
|
||||||
|
CreateUser(ctx context.Context, user *model.User) (*model.User, error)
|
||||||
|
RegisterUser(ctx context.Context, user *model.User, resourceOwner string) (*model.User, error)
|
||||||
|
DeactivateUser(ctx context.Context, id string) (*model.User, error)
|
||||||
|
ReactivateUser(ctx context.Context, id string) (*model.User, error)
|
||||||
|
LockUser(ctx context.Context, id string) (*model.User, error)
|
||||||
|
UnlockUser(ctx context.Context, id string) (*model.User, error)
|
||||||
|
|
||||||
|
SetOneTimePassword(ctx context.Context, password *model.Password) (*model.Password, error)
|
||||||
|
RequestSetPassword(ctx context.Context, id string, notifyType model.NotificationType) error
|
||||||
|
|
||||||
|
ProfileByID(ctx context.Context, userID string) (*model.Profile, error)
|
||||||
|
ChangeProfile(ctx context.Context, profile *model.Profile) (*model.Profile, error)
|
||||||
|
|
||||||
|
EmailByID(ctx context.Context, userID string) (*model.Email, error)
|
||||||
|
ChangeEmail(ctx context.Context, email *model.Email) (*model.Email, error)
|
||||||
|
CreateEmailVerificationCode(ctx context.Context, userID string) error
|
||||||
|
|
||||||
|
PhoneByID(ctx context.Context, userID string) (*model.Phone, error)
|
||||||
|
ChangePhone(ctx context.Context, email *model.Phone) (*model.Phone, error)
|
||||||
|
CreatePhoneVerificationCode(ctx context.Context, userID string) error
|
||||||
|
|
||||||
|
AddressByID(ctx context.Context, userID string) (*model.Address, error)
|
||||||
|
ChangeAddress(ctx context.Context, address *model.Address) (*model.Address, error)
|
||||||
|
}
|
15
internal/management/repository/user_grant.go
Normal file
15
internal/management/repository/user_grant.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepository interface {
|
||||||
|
UserGrantByID(ctx context.Context, grantID string) (*model.UserGrant, error)
|
||||||
|
AddUserGrant(ctx context.Context, grant *model.UserGrant) (*model.UserGrant, error)
|
||||||
|
ChangeUserGrant(ctx context.Context, grant *model.UserGrant) (*model.UserGrant, error)
|
||||||
|
DeactivateUserGrant(ctx context.Context, grantID string) (*model.UserGrant, error)
|
||||||
|
ReactivateUserGrant(ctx context.Context, grantID string) (*model.UserGrant, error)
|
||||||
|
RemoveUserGrant(ctx context.Context, grantID string) error
|
||||||
|
}
|
12
internal/model/mfa.go
Normal file
12
internal/model/mfa.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/crypto"
|
||||||
|
|
||||||
|
type Multifactors struct {
|
||||||
|
OTP OTP
|
||||||
|
}
|
||||||
|
|
||||||
|
type OTP struct {
|
||||||
|
Issuer string
|
||||||
|
CryptoMFA crypto.EncryptionAlgorithm
|
||||||
|
}
|
@ -33,8 +33,8 @@ func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*Projec
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerator.PasswordSaltCost)
|
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
|
||||||
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerator.ClientSecretGenerator, passwordAlg)
|
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
|
||||||
idGenerator := sonyflake.NewSonyflake(sonyflake.Settings{})
|
idGenerator := sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
return &ProjectEventstore{
|
return &ProjectEventstore{
|
||||||
Eventstore: conf.Eventstore,
|
Eventstore: conf.Eventstore,
|
||||||
|
13
internal/user/model/address.go
Normal file
13
internal/user/model/address.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
44
internal/user/model/email.go
Normal file
44
internal/user/model/email.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
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 != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Email) GenerateEmailCodeIfNeeded(emailGenerator crypto.Generator) (*EmailCode, error) {
|
||||||
|
var emailCode *EmailCode
|
||||||
|
if e.IsEmailVerified {
|
||||||
|
return emailCode, nil
|
||||||
|
}
|
||||||
|
emailCode = new(EmailCode)
|
||||||
|
return emailCode, emailCode.GenerateEmailCode(emailGenerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (code *EmailCode) GenerateEmailCode(emailGenerator crypto.Generator) error {
|
||||||
|
emailCodeCrypto, _, err := crypto.NewCode(emailGenerator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
code.Code = emailCodeCrypto
|
||||||
|
code.Expiry = emailGenerator.Expiry()
|
||||||
|
return nil
|
||||||
|
}
|
23
internal/user/model/mfa.go
Normal file
23
internal/user/model/mfa.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type MfaState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
MFASTATE_UNSPECIFIED MfaState = iota
|
||||||
|
MFASTATE_NOTREADY
|
||||||
|
MFASTATE_READY
|
||||||
|
)
|
47
internal/user/model/password.go
Normal file
47
internal/user/model/password.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
NOTIFICATIONTYPE_EMAIL NotificationType = iota
|
||||||
|
NOTIFICATIONTYPE_SMS
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Password) IsValid() bool {
|
||||||
|
return p.AggregateID != "" && p.SecretString != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Password) HashPasswordIfExisting(passwordAlg crypto.HashAlgorithm, onetime bool) error {
|
||||||
|
if p.SecretString == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
secret, err := crypto.Hash([]byte(p.SecretString), passwordAlg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.SecretCrypto = secret
|
||||||
|
p.ChangeRequired = onetime
|
||||||
|
return nil
|
||||||
|
}
|
67
internal/user/model/phone.go
Normal file
67
internal/user/model/phone.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return p.PhoneNumber != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPhoneChangedEvent(event *es_models.Event) error {
|
||||||
|
u.Phone = new(Phone)
|
||||||
|
u.Phone.setData(event)
|
||||||
|
u.IsPhoneVerified = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPhoneVerifiedEvent() error {
|
||||||
|
u.IsPhoneVerified = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Phone) setData(event *es_models.Event) error {
|
||||||
|
p.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, p); err != nil {
|
||||||
|
logging.Log("EVEN-dlo9s").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Phone) GeneratePhoneCodeIfNeeded(phoneGenerator crypto.Generator) (*PhoneCode, error) {
|
||||||
|
var phoneCode *PhoneCode
|
||||||
|
if p.IsPhoneVerified {
|
||||||
|
return phoneCode, nil
|
||||||
|
}
|
||||||
|
phoneCode = new(PhoneCode)
|
||||||
|
return phoneCode, phoneCode.GeneratePhoneCode(phoneGenerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (code *PhoneCode) GeneratePhoneCode(phoneGenerator crypto.Generator) error {
|
||||||
|
phoneCodeCrypto, _, err := crypto.NewCode(phoneGenerator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
code.Code = phoneCodeCrypto
|
||||||
|
code.Expiry = phoneGenerator.Expiry()
|
||||||
|
return nil
|
||||||
|
}
|
22
internal/user/model/profile.go
Normal file
22
internal/user/model/profile.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
UserName string
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
NickName string
|
||||||
|
DisplayName string
|
||||||
|
PreferredLanguage language.Tag
|
||||||
|
Gender Gender
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) IsValid() bool {
|
||||||
|
return p.FirstName != "" && p.LastName != ""
|
||||||
|
}
|
126
internal/user/model/user.go
Normal file
126
internal/user/model/user.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
State UserState
|
||||||
|
*Password
|
||||||
|
*Profile
|
||||||
|
*Email
|
||||||
|
*Phone
|
||||||
|
*Address
|
||||||
|
InitCode *InitUserCode
|
||||||
|
EmailCode *EmailCode
|
||||||
|
PhoneCode *PhoneCode
|
||||||
|
PasswordCode *PasswordCode
|
||||||
|
OTP *OTP
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitUserCode struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue
|
||||||
|
Expiry time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
USERSTATE_UNSPECIFIED UserState = iota
|
||||||
|
USERSTATE_ACTIVE
|
||||||
|
USERSTATE_INACTIVE
|
||||||
|
USERSTATE_DELETED
|
||||||
|
USERSTATE_LOCKED
|
||||||
|
USERSTATE_SUSPEND
|
||||||
|
USERSTATE_INITIAL
|
||||||
|
)
|
||||||
|
|
||||||
|
type Gender int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
GENDER_UNDEFINED Gender = iota
|
||||||
|
GENDER_FEMALE
|
||||||
|
GENDER_MALE
|
||||||
|
GENDER_DIVERSE
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *User) SetEmailAsUsername() {
|
||||||
|
if u.Profile != nil && u.UserName == "" && u.Email != nil {
|
||||||
|
u.UserName = u.EmailAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsValid() bool {
|
||||||
|
return u.Profile != nil && u.FirstName != "" && u.LastName != "" && u.UserName != "" && u.Email != nil && u.EmailAddress != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsInitialState() bool {
|
||||||
|
return u.Email == nil || !u.IsEmailVerified || u.Password == nil || u.SecretString == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsActive() bool {
|
||||||
|
return u.State == USERSTATE_ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsInitial() bool {
|
||||||
|
return u.State == USERSTATE_INITIAL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsInactive() bool {
|
||||||
|
return u.State == USERSTATE_INACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsLocked() bool {
|
||||||
|
return u.State == USERSTATE_LOCKED
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) IsOTPReady() bool {
|
||||||
|
return u.OTP != nil && u.OTP.State == MFASTATE_READY
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) HashPasswordIfExisting(passwordAlg crypto.HashAlgorithm, onetime bool) error {
|
||||||
|
if u.Password != nil {
|
||||||
|
return u.Password.HashPasswordIfExisting(passwordAlg, onetime)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GenerateInitCodeIfNeeded(initGenerator crypto.Generator) error {
|
||||||
|
u.InitCode = new(InitUserCode)
|
||||||
|
if !u.IsInitialState() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.InitCode.GenerateInitUserCode(initGenerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GeneratePhoneCodeIfNeeded(phoneGenerator crypto.Generator) error {
|
||||||
|
u.PhoneCode = new(PhoneCode)
|
||||||
|
if u.Phone == nil || u.IsPhoneVerified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.PhoneCode.GeneratePhoneCode(phoneGenerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GenerateEmailCodeIfNeeded(emailGenerator crypto.Generator) error {
|
||||||
|
u.EmailCode = new(EmailCode)
|
||||||
|
if u.Email == nil || u.IsEmailVerified {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return u.EmailCode.GenerateEmailCode(emailGenerator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (init *InitUserCode) GenerateInitUserCode(generator crypto.Generator) error {
|
||||||
|
initCodeCrypto, _, err := crypto.NewCode(generator)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
init.Code = initCodeCrypto
|
||||||
|
init.Expiry = generator.Expiry()
|
||||||
|
return nil
|
||||||
|
}
|
35
internal/user/repository/eventsourcing/cache.go
Normal file
35
internal/user/repository/eventsourcing/cache.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/cache"
|
||||||
|
"github.com/caos/zitadel/internal/cache/config"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserCache struct {
|
||||||
|
userCache cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartCache(conf *config.CacheConfig) (*UserCache, error) {
|
||||||
|
userCache, err := conf.Config.NewCache()
|
||||||
|
logging.Log("EVENT-vDneN").OnError(err).Panic("unable to create user cache")
|
||||||
|
|
||||||
|
return &UserCache{userCache: userCache}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserCache) getUser(ID string) *model.User {
|
||||||
|
user := &model.User{ObjectRoot: models.ObjectRoot{AggregateID: ID}}
|
||||||
|
if err := c.userCache.Get(ID, user); err != nil {
|
||||||
|
logging.Log("EVENT-4eTZh").WithError(err).Debug("error in getting cache")
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserCache) cacheUser(user *model.User) {
|
||||||
|
err := c.userCache.Set(user.AggregateID, user)
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("EVENT-ThnBb").WithError(err).Debug("error in setting project cache")
|
||||||
|
}
|
||||||
|
}
|
18
internal/user/repository/eventsourcing/codes.go
Normal file
18
internal/user/repository/eventsourcing/codes.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (es *UserEventstore) generatePasswordCode(passwordCode *model.PasswordCode, notifyType usr_model.NotificationType) error {
|
||||||
|
passwordCodeCrypto, _, err := crypto.NewCode(es.PasswordVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
passwordCode.Code = passwordCodeCrypto
|
||||||
|
passwordCode.Expiry = es.PasswordVerificationCode.Expiry()
|
||||||
|
passwordCode.NotificationType = int32(notifyType)
|
||||||
|
return nil
|
||||||
|
}
|
729
internal/user/repository/eventsourcing/eventstore.go
Normal file
729
internal/user/repository/eventsourcing/eventstore.go
Normal file
@ -0,0 +1,729 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/cache/config"
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||||
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
"github.com/pquerna/otp/totp"
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserEventstore struct {
|
||||||
|
es_int.Eventstore
|
||||||
|
userCache *UserCache
|
||||||
|
idGenerator *sonyflake.Sonyflake
|
||||||
|
PasswordAlg crypto.HashAlgorithm
|
||||||
|
InitializeUserCode crypto.Generator
|
||||||
|
EmailVerificationCode crypto.Generator
|
||||||
|
PhoneVerificationCode crypto.Generator
|
||||||
|
PasswordVerificationCode crypto.Generator
|
||||||
|
Multifactors global_model.Multifactors
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserConfig struct {
|
||||||
|
es_int.Eventstore
|
||||||
|
Cache *config.CacheConfig
|
||||||
|
PasswordSaltCost int
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventstore, error) {
|
||||||
|
userCache, err := StartCache(conf.Cache)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idGenerator := sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
initCodeGen := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.InitializeUserCode, aesCrypto)
|
||||||
|
emailVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.EmailVerificationCode, aesCrypto)
|
||||||
|
phoneVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PhoneVerificationCode, aesCrypto)
|
||||||
|
passwordVerificationCode := crypto.NewEncryptionGenerator(systemDefaults.SecretGenerators.PasswordVerificationCode, aesCrypto)
|
||||||
|
aesOtpCrypto, err := crypto.NewAESCrypto(systemDefaults.Multifactors.OTP.VerificationKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mfa := global_model.Multifactors{
|
||||||
|
OTP: global_model.OTP{
|
||||||
|
CryptoMFA: aesOtpCrypto,
|
||||||
|
Issuer: systemDefaults.Multifactors.OTP.Issuer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &UserEventstore{
|
||||||
|
Eventstore: conf.Eventstore,
|
||||||
|
userCache: userCache,
|
||||||
|
idGenerator: idGenerator,
|
||||||
|
InitializeUserCode: initCodeGen,
|
||||||
|
EmailVerificationCode: emailVerificationCode,
|
||||||
|
PhoneVerificationCode: phoneVerificationCode,
|
||||||
|
PasswordVerificationCode: passwordVerificationCode,
|
||||||
|
Multifactors: mfa,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) UserByID(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
user := es.userCache.getUser(id)
|
||||||
|
|
||||||
|
query, err := UserByIDQuery(user.AggregateID, user.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = es_sdk.Filter(ctx, es.FilterEvents, user.AppendEvents, query)
|
||||||
|
if err != nil && caos_errs.IsNotFound(err) && user.Sequence == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(user)
|
||||||
|
return model.UserToModel(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) CreateUser(ctx context.Context, user *usr_model.User) (*usr_model.User, error) {
|
||||||
|
user.SetEmailAsUsername()
|
||||||
|
if !user.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "Name is required")
|
||||||
|
}
|
||||||
|
//TODO: Check Uniqueness
|
||||||
|
id, err := es.idGenerator.NextID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user.AggregateID = strconv.FormatUint(id, 10)
|
||||||
|
|
||||||
|
err = user.HashPasswordIfExisting(es.PasswordAlg, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = user.GenerateInitCodeIfNeeded(es.InitializeUserCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = user.GeneratePhoneCodeIfNeeded(es.PhoneVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
repoInitCode := model.InitCodeFromModel(user.InitCode)
|
||||||
|
repoPhoneCode := model.PhoneCodeFromModel(user.PhoneCode)
|
||||||
|
|
||||||
|
createAggregate := UserCreateAggregate(es.AggregateCreator(), repoUser, repoInitCode, repoPhoneCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, createAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return model.UserToModel(repoUser), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) RegisterUser(ctx context.Context, user *usr_model.User, resourceOwner string) (*usr_model.User, error) {
|
||||||
|
user.SetEmailAsUsername()
|
||||||
|
if !user.IsValid() || user.Password == nil || user.SecretString == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9dk45", "user is invalid")
|
||||||
|
}
|
||||||
|
//TODO: Check Uniqueness
|
||||||
|
id, err := es.idGenerator.NextID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
user.AggregateID = strconv.FormatUint(id, 10)
|
||||||
|
|
||||||
|
err = user.HashPasswordIfExisting(es.PasswordAlg, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = user.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
repoEmailCode := model.EmailCodeFromModel(user.EmailCode)
|
||||||
|
|
||||||
|
createAggregate := UserRegisterAggregate(es.AggregateCreator(), repoUser, resourceOwner, repoEmailCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, createAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return model.UserToModel(repoUser), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) DeactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
existing, err := es.UserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing.IsInactive() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-die45", "cant deactivate inactive user")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
aggregate := UserDeactivateAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.UserToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ReactivateUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
existing, err := es.UserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.IsInactive() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do94s", "user must be inactive")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
aggregate := UserReactivateAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.UserToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) LockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
existing, err := es.UserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.IsActive() && !existing.IsInitial() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di83s", "user must be active or initial")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
aggregate := UserLockAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.UserToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) UnlockUser(ctx context.Context, id string) (*usr_model.User, error) {
|
||||||
|
existing, err := es.UserByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.IsLocked() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dks83", "user must be locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
aggregate := UserUnlockAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, aggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.UserToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) InitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d8diw", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.InitCode != nil {
|
||||||
|
return user.InitCode, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-d8e2", "init code not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) CreateInitializeUserCodeByID(ctx context.Context, userID string) (*usr_model.InitUserCode, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
initCode := new(usr_model.InitUserCode)
|
||||||
|
err = initCode.GenerateInitUserCode(es.InitializeUserCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
repoInitCode := model.InitCodeFromModel(initCode)
|
||||||
|
|
||||||
|
agg := UserInitCodeAggregate(es.AggregateCreator(), repoUser, repoInitCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return model.InitCodeToModel(repoUser.InitCode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) SkipMfaInit(ctx context.Context, userID string) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
agg := SkipMfaAggregate(es.AggregateCreator(), repoUser)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) UserPasswordByID(ctx context.Context, userID string) (*usr_model.Password, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Password != nil {
|
||||||
|
return user.Password, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-d8e2", "password not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) SetOneTimePassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
|
||||||
|
return es.changedPassword(ctx, password, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) SetPassword(ctx context.Context, password *usr_model.Password) (*usr_model.Password, error) {
|
||||||
|
return es.changedPassword(ctx, password, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) changedPassword(ctx context.Context, password *usr_model.Password, onetime bool) (*usr_model.Password, error) {
|
||||||
|
if !password.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dosi3", "password invalid")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, password.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = password.HashPasswordIfExisting(es.PasswordAlg, onetime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
repoPassword := model.PasswordFromModel(password)
|
||||||
|
|
||||||
|
agg := PasswordChangeAggregate(es.AggregateCreator(), repoUser, repoPassword)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
|
||||||
|
return model.PasswordToModel(repoUser.Password), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) RequestSetPassword(ctx context.Context, userID string, notifyType usr_model.NotificationType) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dic8s", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordCode := new(model.PasswordCode)
|
||||||
|
err = es.generatePasswordCode(passwordCode, notifyType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoUser := model.UserFromModel(user)
|
||||||
|
agg := RequestSetPassword(es.AggregateCreator(), repoUser, passwordCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoUser.AppendEvents, agg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.userCache.cacheUser(repoUser)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ProfileByID(ctx context.Context, userID string) (*usr_model.Profile, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Profile != nil {
|
||||||
|
return user.Profile, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dk23f", "profile not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ChangeProfile(ctx context.Context, profile *usr_model.Profile) (*usr_model.Profile, error) {
|
||||||
|
if !profile.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-d82i3", "profile is invalid")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, profile.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoNew := model.ProfileFromModel(profile)
|
||||||
|
|
||||||
|
updateAggregate := ProfileChangeAggregate(es.AggregateCreator(), repoExisting, repoNew)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.ProfileToModel(repoExisting.Profile), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) EmailByID(ctx context.Context, userID string) (*usr_model.Email, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di834", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Email != nil {
|
||||||
|
return user.Email, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dki89", "email not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ChangeEmail(ctx context.Context, email *usr_model.Email) (*usr_model.Email, error) {
|
||||||
|
if !email.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "email is invalid")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, email.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emailCode, err := email.GenerateEmailCodeIfNeeded(es.EmailVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoNew := model.EmailFromModel(email)
|
||||||
|
repoEmailCode := model.EmailCodeFromModel(emailCode)
|
||||||
|
|
||||||
|
updateAggregate := EmailChangeAggregate(es.AggregateCreator(), repoExisting, repoNew, repoEmailCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.EmailToModel(repoExisting.Email), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) VerifyEmail(ctx context.Context, userID, verificationCode string) error {
|
||||||
|
if userID == "" || verificationCode == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9fd", "userId or Code empty")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.EmailCode == nil {
|
||||||
|
return caos_errs.ThrowNotFound(nil, "EVENT-lso9w", "code not found")
|
||||||
|
}
|
||||||
|
if err := crypto.VerifyCode(existing.EmailCode.CreationDate, existing.EmailCode.Expiry, existing.EmailCode.Code, verificationCode, es.EmailVerificationCode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
updateAggregate := EmailVerifiedAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) CreateEmailVerificationCode(ctx context.Context, userID string) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-lco09", "userID missing")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.Email == nil {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "no email existing")
|
||||||
|
}
|
||||||
|
if existing.IsEmailVerified {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-pdo9s", "email already verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
emailCode := new(usr_model.EmailCode)
|
||||||
|
err = emailCode.GenerateEmailCode(es.EmailVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoEmailCode := model.EmailCodeFromModel(emailCode)
|
||||||
|
updateAggregate := EmailVerificationCodeAggregate(es.AggregateCreator(), repoExisting, repoEmailCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) PhoneByID(ctx context.Context, userID string) (*usr_model.Phone, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9se", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Phone != nil {
|
||||||
|
return user.Phone, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-pos9e", "phone not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ChangePhone(ctx context.Context, phone *usr_model.Phone) (*usr_model.Phone, error) {
|
||||||
|
if !phone.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9s4", "phone is invalid")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, phone.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneCode, err := phone.GeneratePhoneCodeIfNeeded(es.PhoneVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoNew := model.PhoneFromModel(phone)
|
||||||
|
repoPhoneCode := model.PhoneCodeFromModel(phoneCode)
|
||||||
|
|
||||||
|
updateAggregate := PhoneChangeAggregate(es.AggregateCreator(), repoExisting, repoNew, repoPhoneCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.PhoneToModel(repoExisting.Phone), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) VerifyPhone(ctx context.Context, userID, verificationCode string) error {
|
||||||
|
if userID == "" || verificationCode == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-dsi8s", "userId or Code empty")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.PhoneCode == nil {
|
||||||
|
return caos_errs.ThrowNotFound(nil, "EVENT-slp0s", "code not found")
|
||||||
|
}
|
||||||
|
if err := crypto.VerifyCode(existing.PhoneCode.CreationDate, existing.PhoneCode.Expiry, existing.PhoneCode.Code, verificationCode, es.PhoneVerificationCode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
updateAggregate := PhoneVerifiedAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) CreatePhoneVerificationCode(ctx context.Context, userID string) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9sw", "userID missing")
|
||||||
|
}
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.Phone == nil {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp9fs", "no phone existing")
|
||||||
|
}
|
||||||
|
if existing.IsPhoneVerified {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sleis", "phone already verified")
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneCode := new(usr_model.PhoneCode)
|
||||||
|
err = phoneCode.GeneratePhoneCode(es.PhoneVerificationCode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoPhoneCode := model.PhoneCodeFromModel(phoneCode)
|
||||||
|
updateAggregate := PhoneVerificationCodeAggregate(es.AggregateCreator(), repoExisting, repoPhoneCode)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) AddressByID(ctx context.Context, userID string) (*usr_model.Address, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-di8ws", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Address != nil {
|
||||||
|
return user.Address, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-so9wa", "address not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) ChangeAddress(ctx context.Context, address *usr_model.Address) (*usr_model.Address, error) {
|
||||||
|
existing, err := es.UserByID(ctx, address.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
repoNew := model.AddressFromModel(address)
|
||||||
|
|
||||||
|
updateAggregate := AddressChangeAggregate(es.AggregateCreator(), repoExisting, repoNew)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return model.AddressToModel(repoExisting.Address), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) OTPByID(ctx context.Context, userID string) (*usr_model.OTP, error) {
|
||||||
|
if userID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-do9se", "userID missing")
|
||||||
|
}
|
||||||
|
user, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.OTP != nil {
|
||||||
|
return user.OTP, nil
|
||||||
|
}
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dps09", "otp not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) AddOTP(ctx context.Context, userID string) (*usr_model.OTP, error) {
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing.IsOTPReady() {
|
||||||
|
return nil, caos_errs.ThrowAlreadyExists(nil, "EVENT-do9se", "user has already configured otp")
|
||||||
|
}
|
||||||
|
key, err := totp.Generate(totp.GenerateOpts{Issuer: es.Multifactors.OTP.Issuer, AccountName: userID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encryptedSecret, err := crypto.Encrypt([]byte(key.Secret()), es.Multifactors.OTP.CryptoMFA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repoOtp := &model.OTP{Secret: encryptedSecret}
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
updateAggregate := MfaOTPAddAggregate(es.AggregateCreator(), repoExisting, repoOtp)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
otp := model.OTPToModel(repoExisting.OTP)
|
||||||
|
otp.Url = key.URL()
|
||||||
|
otp.SecretString = key.Secret()
|
||||||
|
return otp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) RemoveOTP(ctx context.Context, userID string) error {
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.OTP == nil {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "no otp existing")
|
||||||
|
}
|
||||||
|
repoExisting := model.UserFromModel(existing)
|
||||||
|
updateAggregate := MfaOTPRemoveAggregate(es.AggregateCreator(), repoExisting)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, updateAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
es.userCache.cacheUser(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserEventstore) CheckMfaOTP(ctx context.Context, userID, code string) error {
|
||||||
|
existing, err := es.UserByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if existing.OTP == nil {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-sp0de", "no otp existing")
|
||||||
|
}
|
||||||
|
decrypt, err := crypto.DecryptString(existing.OTP.Secret, es.Multifactors.OTP.CryptoMFA)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := totp.Validate(code, decrypt)
|
||||||
|
if !valid {
|
||||||
|
return caos_errs.ThrowInvalidArgument(nil, "EVENT-8isk2", "Invalid code")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
429
internal/user/repository/eventsourcing/eventstore_mock_test.go
Normal file
429
internal/user/repository/eventsourcing/eventstore_mock_test.go
Normal file
@ -0,0 +1,429 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/mock"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserEventstore {
|
||||||
|
return &UserEventstore{
|
||||||
|
Eventstore: mockEs,
|
||||||
|
userCache: GetMockCache(ctrl),
|
||||||
|
idGenerator: GetSonyFlacke(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockedEventstoreWithPw(ctrl *gomock.Controller, mockEs *mock.MockEventstore, init, email, phone, password bool) *UserEventstore {
|
||||||
|
es := &UserEventstore{
|
||||||
|
Eventstore: mockEs,
|
||||||
|
userCache: GetMockCache(ctrl),
|
||||||
|
idGenerator: GetSonyFlacke(),
|
||||||
|
}
|
||||||
|
if init {
|
||||||
|
es.InitializeUserCode = GetMockPwGenerator(ctrl)
|
||||||
|
}
|
||||||
|
if email {
|
||||||
|
es.EmailVerificationCode = GetMockPwGenerator(ctrl)
|
||||||
|
}
|
||||||
|
if phone {
|
||||||
|
es.PhoneVerificationCode = GetMockPwGenerator(ctrl)
|
||||||
|
}
|
||||||
|
if password {
|
||||||
|
es.PasswordVerificationCode = GetMockPwGenerator(ctrl)
|
||||||
|
hash := crypto.NewMockHashAlgorithm(ctrl)
|
||||||
|
hash.EXPECT().Hash(gomock.Any()).Return(nil, nil)
|
||||||
|
hash.EXPECT().Algorithm().Return("bcrypt")
|
||||||
|
es.PasswordAlg = hash
|
||||||
|
}
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockCache(ctrl *gomock.Controller) *UserCache {
|
||||||
|
mockCache := mock_cache.NewMockCache(ctrl)
|
||||||
|
mockCache.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||||
|
mockCache.EXPECT().Set(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||||
|
return &UserCache{userCache: mockCache}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSonyFlacke() *sonyflake.Sonyflake {
|
||||||
|
return sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockPwGenerator(ctrl *gomock.Controller) crypto.Generator {
|
||||||
|
alg := crypto.CreateMockEncryptionAlg(ctrl)
|
||||||
|
generator := crypto.NewMockGenerator(ctrl)
|
||||||
|
generator.EXPECT().Length().Return(uint(10))
|
||||||
|
generator.EXPECT().Runes().Return([]rune("abcdefghijklmnopqrstuvwxyz"))
|
||||||
|
generator.EXPECT().Alg().AnyTimes().Return(alg)
|
||||||
|
generator.EXPECT().Expiry().Return(time.Hour * 1)
|
||||||
|
return generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockUserByIDOK(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockUserByIDNoEvents(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
events := []*es_models.Event{}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUser(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithPWGenerator(ctrl *gomock.Controller, init, email, phone, password bool) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
Email: &model.Email{
|
||||||
|
EmailAddress: "EmailAddress",
|
||||||
|
},
|
||||||
|
Phone: &model.Phone{
|
||||||
|
PhoneNumber: "PhoneNumber",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, init, email, phone, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithInitCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, true, false, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithPasswordAndEmailCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithEmailCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithPhoneCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithPasswordCodeGen(ctrl *gomock.Controller, user model.User) *UserEventstore {
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, false, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithOTPGen(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
es := GetMockedEventstore(ctrl, mockEs)
|
||||||
|
hash := crypto.NewMockEncryptionAlgorithm(ctrl)
|
||||||
|
hash.EXPECT().Algorithm().Return("aes")
|
||||||
|
hash.EXPECT().Encrypt(gomock.Any()).Return(nil, nil)
|
||||||
|
hash.EXPECT().EncryptionKeyID().Return("id")
|
||||||
|
es.Multifactors = global_model.Multifactors{OTP: global_model.OTP{
|
||||||
|
Issuer: "Issuer",
|
||||||
|
CryptoMFA: hash,
|
||||||
|
}}
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateInactiveUser(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 2, Type: model.UserDeactivated},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateLockedUser(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: data},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserLocked},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithInitCode(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
code := model.InitUserCode{Expiry: time.Hour * 30}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
dataCode, _ := json.Marshal(code)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.InitializedUserCodeAdded, Data: dataCode},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithEmailCode(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
Email: &model.Email{
|
||||||
|
EmailAddress: "EmailAddress",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
code := model.EmailCode{Code: &crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
}}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
dataCode, _ := json.Marshal(code)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserEmailCodeAdded, Data: dataCode},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, true, false, false)
|
||||||
|
}
|
||||||
|
func GetMockManipulateUserVerifiedEmail(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
Email: &model.Email{
|
||||||
|
EmailAddress: "EmailAddress",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserEmailVerified},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithPhoneCode(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
Phone: &model.Phone{
|
||||||
|
PhoneNumber: "PhoneNumber",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
code := model.PhoneCode{Code: &crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
}}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
dataCode, _ := json.Marshal(code)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserPhoneCodeAdded, Data: dataCode},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstoreWithPw(ctrl, mockEs, false, false, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserVerifiedPhone(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
Phone: &model.Phone{
|
||||||
|
PhoneNumber: "PhoneNumber",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserPhoneVerified},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
func GetMockManipulateUserFull(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
FirstName: "FirstName",
|
||||||
|
LastName: "LastName",
|
||||||
|
},
|
||||||
|
Password: &model.Password{
|
||||||
|
Secret: &crypto.CryptoValue{Algorithm: "bcrypt", KeyID: "KeyID"},
|
||||||
|
ChangeRequired: true,
|
||||||
|
},
|
||||||
|
Email: &model.Email{
|
||||||
|
EmailAddress: "EmailAddress",
|
||||||
|
},
|
||||||
|
Phone: &model.Phone{
|
||||||
|
PhoneNumber: "PhoneNumber",
|
||||||
|
},
|
||||||
|
Address: &model.Address{
|
||||||
|
Country: "Country",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserWithOTP(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
user := model.User{
|
||||||
|
Profile: &model.Profile{
|
||||||
|
UserName: "UserName",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
otp := model.OTP{Secret: &crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("code"),
|
||||||
|
}}
|
||||||
|
dataUser, _ := json.Marshal(user)
|
||||||
|
dataOtp, _ := json.Marshal(otp)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserAdded, Data: dataUser},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MfaOtpAdded, Data: dataOtp},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserNoEvents(ctrl *gomock.Controller) *UserEventstore {
|
||||||
|
events := []*es_models.Event{}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
2317
internal/user/repository/eventsourcing/eventstore_test.go
Normal file
2317
internal/user/repository/eventsourcing/eventstore_test.go
Normal file
File diff suppressed because it is too large
Load Diff
77
internal/user/repository/eventsourcing/model/address.go
Normal file
77
internal/user/repository/eventsourcing/model/address.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
Locality string `json:"locality,omitempty"`
|
||||||
|
PostalCode string `json:"postalCode,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
StreetAddress string `json:"streetAddress,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) Changes(changed *Address) map[string]interface{} {
|
||||||
|
changes := make(map[string]interface{}, 1)
|
||||||
|
if a.Country != changed.Country {
|
||||||
|
changes["country"] = changed.Country
|
||||||
|
}
|
||||||
|
if a.Locality != changed.Locality {
|
||||||
|
changes["locality"] = changed.Locality
|
||||||
|
}
|
||||||
|
if a.PostalCode != changed.PostalCode {
|
||||||
|
changes["postalCode"] = changed.PostalCode
|
||||||
|
}
|
||||||
|
if a.Region != changed.Region {
|
||||||
|
changes["region"] = changed.Region
|
||||||
|
}
|
||||||
|
if a.StreetAddress != changed.StreetAddress {
|
||||||
|
changes["streetAddress"] = changed.StreetAddress
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddressFromModel(address *model.Address) *Address {
|
||||||
|
return &Address{
|
||||||
|
ObjectRoot: address.ObjectRoot,
|
||||||
|
Country: address.Country,
|
||||||
|
Locality: address.Locality,
|
||||||
|
PostalCode: address.PostalCode,
|
||||||
|
Region: address.Region,
|
||||||
|
StreetAddress: address.StreetAddress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddressToModel(address *Address) *model.Address {
|
||||||
|
return &model.Address{
|
||||||
|
ObjectRoot: address.ObjectRoot,
|
||||||
|
Country: address.Country,
|
||||||
|
Locality: address.Locality,
|
||||||
|
PostalCode: address.PostalCode,
|
||||||
|
Region: address.Region,
|
||||||
|
StreetAddress: address.StreetAddress,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserAddressChangedEvent(event *es_models.Event) error {
|
||||||
|
if u.Address == nil {
|
||||||
|
u.Address = new(Address)
|
||||||
|
}
|
||||||
|
return u.Address.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Address) setData(event *es_models.Event) error {
|
||||||
|
a.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||||
|
logging.Log("EVEN-clos0").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-so92s", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
92
internal/user/repository/eventsourcing/model/address_test.go
Normal file
92
internal/user/repository/eventsourcing/model/address_test.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddressChanges(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing *Address
|
||||||
|
new *Address
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
changesLen int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
|
||||||
|
new: &Address{Country: "CountryChanged", Locality: "LocalityChanged", PostalCode: "PostalCodeChanged", Region: "RegionChanged", StreetAddress: "StreetAddressChanged"},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
|
||||||
|
new: &Address{Country: "Country", Locality: "Locality", PostalCode: "PostalCode", Region: "Region", StreetAddress: "StreetAddress"},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
changes := tt.args.existing.Changes(tt.args.new)
|
||||||
|
if len(changes) != tt.res.changesLen {
|
||||||
|
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserAddressChangedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
address *Address
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user address event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Address: &Address{Locality: "Locality", Country: "Country"}},
|
||||||
|
address: &Address{Locality: "LocalityChanged", PostalCode: "PostalCode"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Address: &Address{Locality: "LocalityChanged", Country: "Country", PostalCode: "PostalCode"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.address != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.address)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserAddressChangedEvent(tt.args.event)
|
||||||
|
if tt.args.user.Address.Locality != tt.result.Address.Locality {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
if tt.args.user.Address.Country != tt.result.Address.Country {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
if tt.args.user.Address.PostalCode != tt.result.Address.PostalCode {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
102
internal/user/repository/eventsourcing/model/email.go
Normal file
102
internal/user/repository/eventsourcing/model/email.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"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/caos/zitadel/internal/user/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Email struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
EmailAddress string `json:"email,omitempty"`
|
||||||
|
IsEmailVerified bool `json:"-"`
|
||||||
|
|
||||||
|
isEmailUnique bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailCode struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Email) Changes(changed *Email) map[string]interface{} {
|
||||||
|
changes := make(map[string]interface{}, 1)
|
||||||
|
if changed.EmailAddress != "" && e.EmailAddress != changed.EmailAddress {
|
||||||
|
changes["email"] = changed.EmailAddress
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailFromModel(email *model.Email) *Email {
|
||||||
|
return &Email{
|
||||||
|
ObjectRoot: email.ObjectRoot,
|
||||||
|
EmailAddress: email.EmailAddress,
|
||||||
|
IsEmailVerified: email.IsEmailVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailToModel(email *Email) *model.Email {
|
||||||
|
return &model.Email{
|
||||||
|
ObjectRoot: email.ObjectRoot,
|
||||||
|
EmailAddress: email.EmailAddress,
|
||||||
|
IsEmailVerified: email.IsEmailVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailCodeFromModel(code *model.EmailCode) *EmailCode {
|
||||||
|
if code == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &EmailCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailCodeToModel(code *EmailCode) *model.EmailCode {
|
||||||
|
return &model.EmailCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserEmailChangedEvent(event *es_models.Event) error {
|
||||||
|
u.Email = new(Email)
|
||||||
|
return u.Email.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserEmailCodeAddedEvent(event *es_models.Event) error {
|
||||||
|
u.EmailCode = new(EmailCode)
|
||||||
|
return u.EmailCode.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserEmailVerifiedEvent() {
|
||||||
|
u.IsEmailVerified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Email) setData(event *es_models.Event) error {
|
||||||
|
a.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||||
|
logging.Log("EVEN-dlo9s").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-sl9xw", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *EmailCode) setData(event *es_models.Event) error {
|
||||||
|
a.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||||
|
logging.Log("EVEN-lo9s").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-s8uws", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
152
internal/user/repository/eventsourcing/model/email_test.go
Normal file
152
internal/user/repository/eventsourcing/model/email_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmailChanges(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing *Email
|
||||||
|
new *Email
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
changesLen int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Email{EmailAddress: "Email", IsEmailVerified: true},
|
||||||
|
new: &Email{EmailAddress: "EmailChanged", IsEmailVerified: false},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Email{EmailAddress: "Email", IsEmailVerified: true},
|
||||||
|
new: &Email{EmailAddress: "Email", IsEmailVerified: false},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
changes := tt.args.existing.Changes(tt.args.new)
|
||||||
|
if len(changes) != tt.res.changesLen {
|
||||||
|
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserEmailChangedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
email *Email
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user email event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Email: &Email{EmailAddress: "EmailAddress"}},
|
||||||
|
email: &Email{EmailAddress: "EmailAddressChanged"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Email: &Email{EmailAddress: "EmailAddressChanged"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.email != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.email)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserEmailChangedEvent(tt.args.event)
|
||||||
|
if tt.args.user.Email.EmailAddress != tt.result.Email.EmailAddress {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserEmailCodeAddedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
code *EmailCode
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user email code added event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Email: &Email{EmailAddress: "EmailAddress"}},
|
||||||
|
code: &EmailCode{Expiry: time.Hour * 1},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{EmailCode: &EmailCode{Expiry: time.Hour * 1}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.code != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.code)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserEmailCodeAddedEvent(tt.args.event)
|
||||||
|
if tt.args.user.EmailCode.Expiry != tt.result.EmailCode.Expiry {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserEmailVerifiedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user email event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Email: &Email{EmailAddress: "EmailAddress"}},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Email: &Email{EmailAddress: "EmailAddress", IsEmailVerified: true}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
tt.args.user.appendUserEmailVerifiedEvent()
|
||||||
|
if tt.args.user.Email.IsEmailVerified != tt.result.Email.IsEmailVerified {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
57
internal/user/repository/eventsourcing/model/mfa.go
Normal file
57
internal/user/repository/eventsourcing/model/mfa.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"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/caos/zitadel/internal/user/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OTP struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Secret *crypto.CryptoValue `json:"otpSecret,omitempty"`
|
||||||
|
State int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func OTPFromModel(otp *model.OTP) *OTP {
|
||||||
|
return &OTP{
|
||||||
|
ObjectRoot: otp.ObjectRoot,
|
||||||
|
Secret: otp.Secret,
|
||||||
|
State: int32(otp.State),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OTPToModel(otp *OTP) *model.OTP {
|
||||||
|
return &model.OTP{
|
||||||
|
ObjectRoot: otp.ObjectRoot,
|
||||||
|
Secret: otp.Secret,
|
||||||
|
State: model.MfaState(otp.State),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendOtpAddedEvent(event *es_models.Event) error {
|
||||||
|
u.OTP = &OTP{
|
||||||
|
State: int32(model.MFASTATE_NOTREADY),
|
||||||
|
}
|
||||||
|
return u.OTP.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendOtpVerifiedEvent() {
|
||||||
|
u.OTP.State = int32(model.MFASTATE_READY)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendOtpRemovedEvent() {
|
||||||
|
u.OTP = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OTP) setData(event *es_models.Event) error {
|
||||||
|
o.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, o); err != nil {
|
||||||
|
logging.Log("EVEN-d9soe").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-lo023", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
109
internal/user/repository/eventsourcing/model/mfa_test.go
Normal file
109
internal/user/repository/eventsourcing/model/mfa_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendMfaOTPAddedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
otp *OTP
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user otp event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFASTATE_NOTREADY)}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.otp != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.otp)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendOtpAddedEvent(tt.args.event)
|
||||||
|
if tt.args.user.OTP.State != tt.result.OTP.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.OTP.State, tt.args.user.OTP.State)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendMfaOTPVerifyEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
otp *OTP
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append otp verify event",
|
||||||
|
args: args{
|
||||||
|
user: &User{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}},
|
||||||
|
otp: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}, State: int32(model.MFASTATE_READY)}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.otp != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.otp)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendOtpVerifiedEvent()
|
||||||
|
if tt.args.user.OTP.State != tt.result.OTP.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.OTP.State, tt.args.user.OTP.State)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendMfaOTPRemoveEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
otp *OTP
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append otp verify event",
|
||||||
|
args: args{
|
||||||
|
user: &User{OTP: &OTP{Secret: &crypto.CryptoValue{KeyID: "KeyID"}}},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.user.appendOtpRemovedEvent()
|
||||||
|
if tt.args.user.OTP != nil {
|
||||||
|
t.Errorf("got wrong result: actual: %v ", tt.result.OTP)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
84
internal/user/repository/eventsourcing/model/password.go
Normal file
84
internal/user/repository/eventsourcing/model/password.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"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/caos/zitadel/internal/user/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Password struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Secret *crypto.CryptoValue `json:"secret,omitempty"`
|
||||||
|
ChangeRequired bool `json:"changeRequired,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasswordCode struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
NotificationType int32 `json:"notificationType,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordFromModel(password *model.Password) *Password {
|
||||||
|
return &Password{
|
||||||
|
ObjectRoot: password.ObjectRoot,
|
||||||
|
Secret: password.SecretCrypto,
|
||||||
|
ChangeRequired: password.ChangeRequired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordToModel(password *Password) *model.Password {
|
||||||
|
return &model.Password{
|
||||||
|
ObjectRoot: password.ObjectRoot,
|
||||||
|
SecretCrypto: password.Secret,
|
||||||
|
ChangeRequired: password.ChangeRequired,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordCodeToModel(code *PasswordCode) *model.PasswordCode {
|
||||||
|
return &model.PasswordCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
NotificationType: model.NotificationType(code.NotificationType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPasswordChangedEvent(event *es_models.Event) error {
|
||||||
|
u.Password = new(Password)
|
||||||
|
err := u.Password.setData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.Password.ObjectRoot.CreationDate = event.CreationDate
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendPasswordSetRequestedEvent(event *es_models.Event) error {
|
||||||
|
u.PasswordCode = new(PasswordCode)
|
||||||
|
return u.PasswordCode.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pw *Password) setData(event *es_models.Event) error {
|
||||||
|
pw.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, pw); err != nil {
|
||||||
|
logging.Log("EVEN-dks93").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-sl9xlo2rsw", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *PasswordCode) setData(event *es_models.Event) error {
|
||||||
|
a.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||||
|
logging.Log("EVEN-lo0y2").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-q21dr", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendUserPasswordChangedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
pw *Password
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append init user code event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
pw: &Password{ChangeRequired: true},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Password: &Password{ChangeRequired: true}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.pw != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.pw)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserPasswordChangedEvent(tt.args.event)
|
||||||
|
if tt.args.user.Password.ChangeRequired != tt.result.Password.ChangeRequired {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendPasswordSetRequestedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
code *PasswordCode
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user phone code added event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
|
||||||
|
code: &PasswordCode{Expiry: time.Hour * 1},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{PasswordCode: &PasswordCode{Expiry: time.Hour * 1}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.code != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.code)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendPasswordSetRequestedEvent(tt.args.event)
|
||||||
|
if tt.args.user.PasswordCode.Expiry != tt.result.PasswordCode.Expiry {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
100
internal/user/repository/eventsourcing/model/phone.go
Normal file
100
internal/user/repository/eventsourcing/model/phone.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"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/caos/zitadel/internal/user/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Phone struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
PhoneNumber string `json:"phone,omitempty"`
|
||||||
|
IsPhoneVerified bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PhoneCode struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Phone) Changes(changed *Phone) map[string]interface{} {
|
||||||
|
changes := make(map[string]interface{}, 1)
|
||||||
|
if changed.PhoneNumber != "" && p.PhoneNumber != changed.PhoneNumber {
|
||||||
|
changes["phone"] = changed.PhoneNumber
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneFromModel(phone *model.Phone) *Phone {
|
||||||
|
return &Phone{
|
||||||
|
ObjectRoot: phone.ObjectRoot,
|
||||||
|
PhoneNumber: phone.PhoneNumber,
|
||||||
|
IsPhoneVerified: phone.IsPhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneToModel(phone *Phone) *model.Phone {
|
||||||
|
return &model.Phone{
|
||||||
|
ObjectRoot: phone.ObjectRoot,
|
||||||
|
PhoneNumber: phone.PhoneNumber,
|
||||||
|
IsPhoneVerified: phone.IsPhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneCodeFromModel(code *model.PhoneCode) *PhoneCode {
|
||||||
|
if code == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &PhoneCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneCodeToModel(code *PhoneCode) *model.PhoneCode {
|
||||||
|
return &model.PhoneCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPhoneChangedEvent(event *es_models.Event) error {
|
||||||
|
u.Phone = new(Phone)
|
||||||
|
return u.Phone.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPhoneCodeAddedEvent(event *es_models.Event) error {
|
||||||
|
u.PhoneCode = new(PhoneCode)
|
||||||
|
return u.PhoneCode.setData(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUserPhoneVerifiedEvent() {
|
||||||
|
u.IsPhoneVerified = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Phone) setData(event *es_models.Event) error {
|
||||||
|
p.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, p); err != nil {
|
||||||
|
logging.Log("EVEN-lco9s").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-lre56", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *PhoneCode) setData(event *es_models.Event) error {
|
||||||
|
a.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, a); err != nil {
|
||||||
|
logging.Log("EVEN-sk8ws").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-7hdj3", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
152
internal/user/repository/eventsourcing/model/phone_test.go
Normal file
152
internal/user/repository/eventsourcing/model/phone_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPhoneChanges(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing *Phone
|
||||||
|
new *Phone
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
changesLen int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Phone{PhoneNumber: "Phone", IsPhoneVerified: true},
|
||||||
|
new: &Phone{PhoneNumber: "PhoneChanged", IsPhoneVerified: false},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no fields changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Phone{PhoneNumber: "Phone", IsPhoneVerified: true},
|
||||||
|
new: &Phone{PhoneNumber: "Phone", IsPhoneVerified: false},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
changes := tt.args.existing.Changes(tt.args.new)
|
||||||
|
if len(changes) != tt.res.changesLen {
|
||||||
|
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserPhoneChangedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
phone *Phone
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user phone event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
|
||||||
|
phone: &Phone{PhoneNumber: "PhoneNumberChanged"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Phone: &Phone{PhoneNumber: "PhoneNumberChanged"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.phone != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.phone)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserPhoneChangedEvent(tt.args.event)
|
||||||
|
if tt.args.user.Phone.PhoneNumber != tt.result.Phone.PhoneNumber {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserPhoneCodeAddedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
code *PhoneCode
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user phone code added event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
|
||||||
|
code: &PhoneCode{Expiry: time.Hour * 1},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{PhoneCode: &PhoneCode{Expiry: time.Hour * 1}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.code != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.code)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendUserPhoneCodeAddedEvent(tt.args.event)
|
||||||
|
if tt.args.user.PhoneCode.Expiry != tt.result.PhoneCode.Expiry {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUserPhoneVerifiedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append user phone event",
|
||||||
|
args: args{
|
||||||
|
user: &User{Phone: &Phone{PhoneNumber: "PhoneNumber"}},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{Phone: &Phone{PhoneNumber: "PhoneNumber", IsPhoneVerified: true}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
tt.args.user.appendUserPhoneVerifiedEvent()
|
||||||
|
if tt.args.user.Phone.IsPhoneVerified != tt.result.Phone.IsPhoneVerified {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
70
internal/user/repository/eventsourcing/model/profile.go
Normal file
70
internal/user/repository/eventsourcing/model/profile.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Profile struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
UserName string `json:"userName,omitempty"`
|
||||||
|
FirstName string `json:"firstName,omitempty"`
|
||||||
|
LastName string `json:"lastName,omitempty"`
|
||||||
|
NickName string `json:"nickName,omitempty"`
|
||||||
|
DisplayName string `json:"displayName,omitempty"`
|
||||||
|
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
|
||||||
|
Gender int32 `json:"gender,omitempty"`
|
||||||
|
|
||||||
|
isUserNameUnique bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Profile) Changes(changed *Profile) map[string]interface{} {
|
||||||
|
changes := make(map[string]interface{}, 1)
|
||||||
|
if changed.FirstName != "" && p.FirstName != changed.FirstName {
|
||||||
|
changes["firstName"] = changed.FirstName
|
||||||
|
}
|
||||||
|
if changed.LastName != "" && p.LastName != changed.LastName {
|
||||||
|
changes["lastName"] = changed.LastName
|
||||||
|
}
|
||||||
|
if changed.NickName != p.NickName {
|
||||||
|
changes["nickName"] = changed.NickName
|
||||||
|
}
|
||||||
|
if changed.DisplayName != p.DisplayName {
|
||||||
|
changes["displayName"] = changed.DisplayName
|
||||||
|
}
|
||||||
|
if p.PreferredLanguage != language.Und && changed.PreferredLanguage != p.PreferredLanguage {
|
||||||
|
changes["preferredLanguage"] = changed.PreferredLanguage
|
||||||
|
}
|
||||||
|
if p.Gender > 0 && changed.Gender != p.Gender {
|
||||||
|
changes["gender"] = changed.Gender
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProfileFromModel(profile *model.Profile) *Profile {
|
||||||
|
return &Profile{
|
||||||
|
ObjectRoot: profile.ObjectRoot,
|
||||||
|
UserName: profile.UserName,
|
||||||
|
FirstName: profile.FirstName,
|
||||||
|
LastName: profile.LastName,
|
||||||
|
NickName: profile.NickName,
|
||||||
|
DisplayName: profile.DisplayName,
|
||||||
|
PreferredLanguage: profile.PreferredLanguage,
|
||||||
|
Gender: int32(profile.Gender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProfileToModel(profile *Profile) *model.Profile {
|
||||||
|
return &model.Profile{
|
||||||
|
ObjectRoot: profile.ObjectRoot,
|
||||||
|
UserName: profile.UserName,
|
||||||
|
FirstName: profile.FirstName,
|
||||||
|
LastName: profile.LastName,
|
||||||
|
NickName: profile.NickName,
|
||||||
|
DisplayName: profile.DisplayName,
|
||||||
|
PreferredLanguage: profile.PreferredLanguage,
|
||||||
|
Gender: model.Gender(profile.Gender),
|
||||||
|
}
|
||||||
|
}
|
71
internal/user/repository/eventsourcing/model/profile_test.go
Normal file
71
internal/user/repository/eventsourcing/model/profile_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
user_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProfileChanges(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing *Profile
|
||||||
|
new *Profile
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
changesLen int
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all attributes changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Profile{UserName: "UserName", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
new: &Profile{UserName: "UserNameChanged", FirstName: "FirstNameChanged", LastName: "LastNameChanged", NickName: "NickNameChanged", DisplayName: "DisplayNameChanged", PreferredLanguage: language.English, Gender: int32(user_model.GENDER_MALE)},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no changes",
|
||||||
|
args: args{
|
||||||
|
existing: &Profile{UserName: "UserName", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
new: &Profile{UserName: "UserName", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "username changed",
|
||||||
|
args: args{
|
||||||
|
existing: &Profile{UserName: "UserName", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
new: &Profile{UserName: "UserNameChanged", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty names",
|
||||||
|
args: args{
|
||||||
|
existing: &Profile{UserName: "UserName", FirstName: "FirstName", LastName: "LastName", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
new: &Profile{UserName: "UserName", FirstName: "", LastName: "", NickName: "NickName", DisplayName: "DisplayName", PreferredLanguage: language.German, Gender: int32(user_model.GENDER_FEMALE)},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
changesLen: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
changes := tt.args.existing.Changes(tt.args.new)
|
||||||
|
if len(changes) != tt.res.changesLen {
|
||||||
|
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
47
internal/user/repository/eventsourcing/model/types.go
Normal file
47
internal/user/repository/eventsourcing/model/types.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserAggregate models.AggregateType = "user"
|
||||||
|
UserUserNameAggregate models.AggregateType = "user.username"
|
||||||
|
UserEmailAggregate models.AggregateType = "user.email"
|
||||||
|
|
||||||
|
UserAdded models.EventType = "user.added"
|
||||||
|
UserRegistered models.EventType = "user.selfregistered"
|
||||||
|
InitializedUserCodeAdded models.EventType = "user.initialization.code.added"
|
||||||
|
InitializedUserCodeSent models.EventType = "user.initialization.code.sent"
|
||||||
|
|
||||||
|
UserUserNameReserved models.EventType = "user.username.reserved"
|
||||||
|
UserUserNameReleased models.EventType = "user.username.released"
|
||||||
|
UserEmailReserved models.EventType = "user.email.reserved"
|
||||||
|
UserEmailReleased models.EventType = "user.email.released"
|
||||||
|
|
||||||
|
UserLocked models.EventType = "user.locked"
|
||||||
|
UserUnlocked models.EventType = "user.unlocked"
|
||||||
|
UserDeactivated models.EventType = "user.deactivated"
|
||||||
|
UserReactivated models.EventType = "user.reactivated"
|
||||||
|
UserDeleted models.EventType = "user.deleted"
|
||||||
|
|
||||||
|
UserPasswordChanged models.EventType = "user.password.changed"
|
||||||
|
UserPasswordCodeAdded models.EventType = "user.password.code.added"
|
||||||
|
UserPasswordCodeSent models.EventType = "user.password.code.sent"
|
||||||
|
|
||||||
|
UserEmailChanged models.EventType = "user.email.changed"
|
||||||
|
UserEmailVerified models.EventType = "user.email.verified"
|
||||||
|
UserEmailCodeAdded models.EventType = "user.email.code.added"
|
||||||
|
UserEmailCodeSent models.EventType = "user.email.code.sent"
|
||||||
|
|
||||||
|
UserPhoneChanged models.EventType = "user.phone.changed"
|
||||||
|
UserPhoneVerified models.EventType = "user.phone.verified"
|
||||||
|
UserPhoneCodeAdded models.EventType = "user.phone.code.added"
|
||||||
|
UserPhoneCodeSent models.EventType = "user.phone.code.sent"
|
||||||
|
|
||||||
|
UserProfileChanged models.EventType = "user.profile.changed"
|
||||||
|
UserAddressChanged models.EventType = "user.address.changed"
|
||||||
|
|
||||||
|
MfaOtpAdded models.EventType = "user.mfa.otp.added"
|
||||||
|
MfaOtpVerified models.EventType = "user.mfa.otp.verified"
|
||||||
|
MfaOtpRemoved models.EventType = "user.mfa.otp.removed"
|
||||||
|
MfaInitSkipped models.EventType = "user.mfa.init.skipped"
|
||||||
|
)
|
246
internal/user/repository/eventsourcing/model/user.go
Normal file
246
internal/user/repository/eventsourcing/model/user.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"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/caos/zitadel/internal/user/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
State int32 `json:"-"`
|
||||||
|
*Password
|
||||||
|
*Profile
|
||||||
|
*Email
|
||||||
|
*Phone
|
||||||
|
*Address
|
||||||
|
InitCode *InitUserCode
|
||||||
|
EmailCode *EmailCode
|
||||||
|
PhoneCode *PhoneCode
|
||||||
|
PasswordCode *PasswordCode
|
||||||
|
OTP *OTP
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitUserCode struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserFromModel(user *model.User) *User {
|
||||||
|
converted := &User{
|
||||||
|
ObjectRoot: user.ObjectRoot,
|
||||||
|
State: int32(user.State),
|
||||||
|
}
|
||||||
|
if user.Password != nil {
|
||||||
|
converted.Password = PasswordFromModel(user.Password)
|
||||||
|
}
|
||||||
|
if user.Profile != nil {
|
||||||
|
converted.Profile = ProfileFromModel(user.Profile)
|
||||||
|
}
|
||||||
|
if user.Email != nil {
|
||||||
|
converted.Email = EmailFromModel(user.Email)
|
||||||
|
}
|
||||||
|
if user.Phone != nil {
|
||||||
|
converted.Phone = PhoneFromModel(user.Phone)
|
||||||
|
}
|
||||||
|
if user.Address != nil {
|
||||||
|
converted.Address = AddressFromModel(user.Address)
|
||||||
|
}
|
||||||
|
if user.OTP != nil {
|
||||||
|
converted.OTP = OTPFromModel(user.OTP)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserToModel(user *User) *model.User {
|
||||||
|
converted := &model.User{
|
||||||
|
ObjectRoot: user.ObjectRoot,
|
||||||
|
State: model.UserState(user.State),
|
||||||
|
}
|
||||||
|
if user.Password != nil {
|
||||||
|
converted.Password = PasswordToModel(user.Password)
|
||||||
|
}
|
||||||
|
if user.Profile != nil {
|
||||||
|
converted.Profile = ProfileToModel(user.Profile)
|
||||||
|
}
|
||||||
|
if user.Email != nil {
|
||||||
|
converted.Email = EmailToModel(user.Email)
|
||||||
|
}
|
||||||
|
if user.Phone != nil {
|
||||||
|
converted.Phone = PhoneToModel(user.Phone)
|
||||||
|
}
|
||||||
|
if user.Address != nil {
|
||||||
|
converted.Address = AddressToModel(user.Address)
|
||||||
|
}
|
||||||
|
if user.InitCode != nil {
|
||||||
|
converted.InitCode = InitCodeToModel(user.InitCode)
|
||||||
|
}
|
||||||
|
if user.EmailCode != nil {
|
||||||
|
converted.EmailCode = EmailCodeToModel(user.EmailCode)
|
||||||
|
}
|
||||||
|
if user.PhoneCode != nil {
|
||||||
|
converted.PhoneCode = PhoneCodeToModel(user.PhoneCode)
|
||||||
|
}
|
||||||
|
if user.PasswordCode != nil {
|
||||||
|
converted.PasswordCode = PasswordCodeToModel(user.PasswordCode)
|
||||||
|
}
|
||||||
|
if user.OTP != nil {
|
||||||
|
converted.OTP = OTPToModel(user.OTP)
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitCodeFromModel(code *model.InitUserCode) *InitUserCode {
|
||||||
|
if code == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &InitUserCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitCodeToModel(code *InitUserCode) *model.InitUserCode {
|
||||||
|
return &model.InitUserCode{
|
||||||
|
ObjectRoot: code.ObjectRoot,
|
||||||
|
Expiry: code.Expiry,
|
||||||
|
Code: code.Code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *User) AppendEvents(events ...*es_models.Event) error {
|
||||||
|
for _, event := range events {
|
||||||
|
if err := p.AppendEvent(event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) AppendEvent(event *es_models.Event) (err error) {
|
||||||
|
u.ObjectRoot.AppendEvent(event)
|
||||||
|
switch event.Type {
|
||||||
|
case UserAdded,
|
||||||
|
UserRegistered,
|
||||||
|
UserProfileChanged:
|
||||||
|
u.setData(event)
|
||||||
|
case UserDeactivated:
|
||||||
|
u.appendDeactivatedEvent()
|
||||||
|
case UserReactivated:
|
||||||
|
u.appendReactivatedEvent()
|
||||||
|
case UserLocked:
|
||||||
|
u.appendLockedEvent()
|
||||||
|
case UserUnlocked:
|
||||||
|
u.appendUnlockedEvent()
|
||||||
|
case InitializedUserCodeAdded:
|
||||||
|
u.appendInitUsercodeCreatedEvent(event)
|
||||||
|
case UserPasswordChanged:
|
||||||
|
err = u.appendUserPasswordChangedEvent(event)
|
||||||
|
case UserPasswordCodeAdded:
|
||||||
|
err = u.appendPasswordSetRequestedEvent(event)
|
||||||
|
case UserEmailChanged:
|
||||||
|
err = u.appendUserEmailChangedEvent(event)
|
||||||
|
case UserEmailCodeAdded:
|
||||||
|
err = u.appendUserEmailCodeAddedEvent(event)
|
||||||
|
case UserEmailVerified:
|
||||||
|
u.appendUserEmailVerifiedEvent()
|
||||||
|
case UserPhoneChanged:
|
||||||
|
err = u.appendUserPhoneChangedEvent(event)
|
||||||
|
case UserPhoneCodeAdded:
|
||||||
|
err = u.appendUserPhoneCodeAddedEvent(event)
|
||||||
|
case UserPhoneVerified:
|
||||||
|
u.appendUserPhoneVerifiedEvent()
|
||||||
|
case UserAddressChanged:
|
||||||
|
err = u.appendUserAddressChangedEvent(event)
|
||||||
|
case MfaOtpAdded:
|
||||||
|
err = u.appendOtpAddedEvent(event)
|
||||||
|
case MfaOtpVerified:
|
||||||
|
u.appendOtpVerifiedEvent()
|
||||||
|
case MfaOtpRemoved:
|
||||||
|
u.appendOtpRemovedEvent()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.ComputeObject()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) ComputeObject() {
|
||||||
|
if u.State == 0 {
|
||||||
|
if u.Email != nil && u.IsEmailVerified {
|
||||||
|
u.State = int32(model.USERSTATE_ACTIVE)
|
||||||
|
} else {
|
||||||
|
u.State = int32(model.USERSTATE_INITIAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u.Password != nil && u.Password.ObjectRoot.IsZero() {
|
||||||
|
u.Password.ObjectRoot = u.ObjectRoot
|
||||||
|
}
|
||||||
|
if u.Profile != nil && u.Profile.ObjectRoot.IsZero() {
|
||||||
|
u.Profile.ObjectRoot = u.ObjectRoot
|
||||||
|
}
|
||||||
|
if u.Email != nil && u.Email.ObjectRoot.IsZero() {
|
||||||
|
u.Email.ObjectRoot = u.ObjectRoot
|
||||||
|
}
|
||||||
|
if u.Phone != nil && u.Phone.ObjectRoot.IsZero() {
|
||||||
|
u.Phone.ObjectRoot = u.ObjectRoot
|
||||||
|
}
|
||||||
|
if u.Address != nil && u.Address.ObjectRoot.IsZero() {
|
||||||
|
u.Address.ObjectRoot = u.ObjectRoot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) setData(event *es_models.Event) error {
|
||||||
|
if err := json.Unmarshal(event.Data, u); err != nil {
|
||||||
|
logging.Log("EVEN-8ujgd").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-sj4jd", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendDeactivatedEvent() {
|
||||||
|
u.State = int32(model.USERSTATE_INACTIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendReactivatedEvent() {
|
||||||
|
u.State = int32(model.USERSTATE_ACTIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendLockedEvent() {
|
||||||
|
u.State = int32(model.USERSTATE_LOCKED)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendUnlockedEvent() {
|
||||||
|
u.State = int32(model.USERSTATE_ACTIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) appendInitUsercodeCreatedEvent(event *es_models.Event) error {
|
||||||
|
initCode := new(InitUserCode)
|
||||||
|
err := initCode.setData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
initCode.ObjectRoot.CreationDate = event.CreationDate
|
||||||
|
u.InitCode = initCode
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *InitUserCode) setData(event *es_models.Event) error {
|
||||||
|
c.ObjectRoot.AppendEvent(event)
|
||||||
|
if err := json.Unmarshal(event.Data, c); err != nil {
|
||||||
|
logging.Log("EVEN-7duwe").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-lo34s", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
152
internal/user/repository/eventsourcing/model/user_test.go
Normal file
152
internal/user/repository/eventsourcing/model/user_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendDeactivatedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append deactivate event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
},
|
||||||
|
result: &User{State: int32(model.USERSTATE_INACTIVE)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.user.appendDeactivatedEvent()
|
||||||
|
if tt.args.user.State != tt.result.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendReactivatedEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append reactivate event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
},
|
||||||
|
result: &User{State: int32(model.USERSTATE_ACTIVE)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.user.appendReactivatedEvent()
|
||||||
|
if tt.args.user.State != tt.result.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendLockEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append lock event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
},
|
||||||
|
result: &User{State: int32(model.USERSTATE_LOCKED)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.user.appendLockedEvent()
|
||||||
|
if tt.args.user.State != tt.result.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendUnlockEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append unlock event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
},
|
||||||
|
result: &User{State: int32(model.USERSTATE_ACTIVE)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.user.appendUnlockedEvent()
|
||||||
|
if tt.args.user.State != tt.result.State {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendInitUserCodeEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
user *User
|
||||||
|
code *InitUserCode
|
||||||
|
event *es_models.Event
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *User
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append init user code event",
|
||||||
|
args: args{
|
||||||
|
user: &User{},
|
||||||
|
code: &InitUserCode{Expiry: time.Hour * 30},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
},
|
||||||
|
result: &User{InitCode: &InitUserCode{Expiry: time.Hour * 30}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.args.code != nil {
|
||||||
|
data, _ := json.Marshal(tt.args.code)
|
||||||
|
tt.args.event.Data = data
|
||||||
|
}
|
||||||
|
tt.args.user.appendInitUsercodeCreatedEvent(tt.args.event)
|
||||||
|
if tt.args.user.InitCode.Expiry != tt.result.InitCode.Expiry {
|
||||||
|
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, tt.args.user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
348
internal/user/repository/eventsourcing/user.go
Normal file
348
internal/user/repository/eventsourcing/user.go
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
|
||||||
|
if id == "" {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d8isw", "id should be filled")
|
||||||
|
}
|
||||||
|
return UserQuery(latestSequence).
|
||||||
|
AggregateIDFilter(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserQuery(latestSequence uint64) *es_models.SearchQuery {
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.UserAggregate).
|
||||||
|
LatestSequenceFilter(latestSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User) (*es_models.Aggregate, error) {
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dis83", "existing user should not be nil")
|
||||||
|
}
|
||||||
|
return aggCreator.NewAggregate(ctx, user.AggregateID, model.UserAggregate, model.UserVersion, user.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserAggregateOverwriteContext(ctx context.Context, aggCreator *es_models.AggregateCreator, user *model.User, resourceOwnerID string, userID string) (*es_models.Aggregate, error) {
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dis83", "existing user should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
return aggCreator.NewAggregate(ctx, user.AggregateID, model.UserAggregate, model.UserVersion, user.Sequence, es_models.OverwriteResourceOwner(resourceOwnerID), es_models.OverwriteEditorUser(userID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserCreateAggregate(aggCreator *es_models.AggregateCreator, user *model.User, initCode *model.InitUserCode, phoneCode *model.PhoneCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-duxk2", "user should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, err = agg.AppendEvent(model.UserAdded, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user.Email != nil && user.EmailAddress != "" && user.IsEmailVerified {
|
||||||
|
agg, err = agg.AppendEvent(model.UserEmailVerified, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user.Phone != nil && user.PhoneNumber != "" && user.IsPhoneVerified {
|
||||||
|
agg, err = agg.AppendEvent(model.UserPhoneVerified, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if user.Password != nil {
|
||||||
|
agg, err = agg.AppendEvent(model.UserPasswordCodeAdded, user.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if initCode != nil {
|
||||||
|
agg, err = agg.AppendEvent(model.InitializedUserCodeAdded, initCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if phoneCode != nil {
|
||||||
|
agg, err = agg.AppendEvent(model.UserPhoneCodeAdded, phoneCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return agg, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserRegisterAggregate(aggCreator *es_models.AggregateCreator, user *model.User, resourceOwner string, emailCode *model.EmailCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if user == nil || resourceOwner == "" || emailCode == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-duxk2", "user, resourceowner, emailcode should not be nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, err := UserAggregateOverwriteContext(ctx, aggCreator, user, resourceOwner, user.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, err = agg.AppendEvent(model.UserRegistered, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserEmailCodeAdded, emailCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserDeactivateAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return userStateAggregate(aggCreator, user, model.UserDeactivated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserReactivateAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return userStateAggregate(aggCreator, user, model.UserReactivated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserLockAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return userStateAggregate(aggCreator, user, model.UserLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserUnlockAggregate(aggCreator *es_models.AggregateCreator, user *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return userStateAggregate(aggCreator, user, model.UserUnlocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
func userStateAggregate(aggCreator *es_models.AggregateCreator, user *model.User, state es_models.EventType) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(state, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserInitCodeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, code *model.InitUserCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if code == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d8i23", "code should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.InitializedUserCodeAdded, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipMfaAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.MfaInitSkipped, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, password *model.Password) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if password == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d9832", "password should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserPasswordChanged, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequestSetPassword(aggCreator *es_models.AggregateCreator, existing *model.User, request *model.PasswordCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if request == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d8ei2", "password set request should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserPasswordCodeAdded, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProfileChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, profile *model.Profile) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if profile == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dhr74", "profile should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changes := existing.Profile.Changes(profile)
|
||||||
|
return agg.AppendEvent(model.UserProfileChanged, changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, email *model.Email, code *model.EmailCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if email == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dki8s", "email should not be nil")
|
||||||
|
}
|
||||||
|
if (!email.IsEmailVerified && code == nil) || (email.IsEmailVerified && code != nil) {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-id934", "email has to be verified or code must be sent")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changes := existing.Email.Changes(email)
|
||||||
|
agg, err = agg.AppendEvent(model.UserEmailChanged, changes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing.Email == nil {
|
||||||
|
existing.Email = new(model.Email)
|
||||||
|
}
|
||||||
|
if email.IsEmailVerified {
|
||||||
|
return agg.AppendEvent(model.UserEmailVerified, code)
|
||||||
|
}
|
||||||
|
if code != nil {
|
||||||
|
return agg.AppendEvent(model.UserEmailCodeAdded, code)
|
||||||
|
}
|
||||||
|
return agg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func EmailVerifiedAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserEmailVerified, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EmailVerificationCodeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, code *model.EmailCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if code == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dki8s", "code should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserEmailCodeAdded, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, phone *model.Phone, code *model.PhoneCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if phone == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dkso3", "phone should not be nil")
|
||||||
|
}
|
||||||
|
if (!phone.IsPhoneVerified && code == nil) || (phone.IsPhoneVerified && code != nil) {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dksi8", "phone has to be verified or code must be sent")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing.Phone == nil {
|
||||||
|
existing.Phone = new(model.Phone)
|
||||||
|
}
|
||||||
|
changes := existing.Phone.Changes(phone)
|
||||||
|
agg, err = agg.AppendEvent(model.UserPhoneChanged, changes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if phone.IsPhoneVerified {
|
||||||
|
return agg.AppendEvent(model.UserPhoneVerified, code)
|
||||||
|
}
|
||||||
|
if code != nil {
|
||||||
|
return agg.AppendEvent(model.UserPhoneCodeAdded, code)
|
||||||
|
}
|
||||||
|
return agg, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func PhoneVerifiedAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserPhoneVerified, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PhoneVerificationCodeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, code *model.PhoneCode) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if code == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dsue2", "code should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserPhoneCodeAdded, code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddressChangeAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, address *model.Address) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if address == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dkx9s", "address should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existing.Address == nil {
|
||||||
|
existing.Address = new(model.Address)
|
||||||
|
}
|
||||||
|
changes := existing.Address.Changes(address)
|
||||||
|
return agg.AppendEvent(model.UserAddressChanged, changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MfaOTPAddAggregate(aggCreator *es_models.AggregateCreator, existing *model.User, otp *model.OTP) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if otp == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dkx9s", "otp should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.MfaOtpAdded, otp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MfaOTPVerifyAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.MfaOtpVerified, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MfaOTPRemoveAggregate(aggCreator *es_models.AggregateCreator, existing *model.User) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.MfaOtpRemoved, nil)
|
||||||
|
}
|
||||||
|
}
|
1574
internal/user/repository/eventsourcing/user_test.go
Normal file
1574
internal/user/repository/eventsourcing/user_test.go
Normal file
File diff suppressed because it is too large
Load Diff
32
internal/usergrant/model/user_grant.go
Normal file
32
internal/usergrant/model/user_grant.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
State UserGrantState
|
||||||
|
UserID string
|
||||||
|
ProjectID string
|
||||||
|
RoleKeys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGrantState int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
USERGRANTSTATE_ACTIVE UserGrantState = iota
|
||||||
|
USERGRANTSTATE_INACTIVE
|
||||||
|
USERGRANTSTATE_REMOVED
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *UserGrant) IsValid() bool {
|
||||||
|
return u.ProjectID != "" && u.UserID != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) IsActive() bool {
|
||||||
|
return u.State == USERGRANTSTATE_ACTIVE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) IsInactive() bool {
|
||||||
|
return u.State == USERGRANTSTATE_INACTIVE
|
||||||
|
}
|
34
internal/usergrant/repository/eventsourcing/cache.go
Normal file
34
internal/usergrant/repository/eventsourcing/cache.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/cache"
|
||||||
|
"github.com/caos/zitadel/internal/cache/config"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantCache struct {
|
||||||
|
userGrantCache cache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartCache(conf *config.CacheConfig) (*UserGrantCache, error) {
|
||||||
|
userGrantCache, err := conf.Config.NewCache()
|
||||||
|
logging.Log("EVENT-vDneN").OnError(err).Panic("unable to create user cache")
|
||||||
|
|
||||||
|
return &UserGrantCache{userGrantCache: userGrantCache}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserGrantCache) getUserGrant(ID string) *model.UserGrant {
|
||||||
|
user := &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: ID}}
|
||||||
|
err := c.userGrantCache.Get(ID, user)
|
||||||
|
logging.Log("EVENT-4eTZh").OnError(err).Debug("error in getting cache")
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserGrantCache) cacheUserGrant(grant *model.UserGrant) {
|
||||||
|
err := c.userGrantCache.Set(grant.AggregateID, grant)
|
||||||
|
|
||||||
|
logging.Log("EVENT-ThnBb").OnError(err).Debug("error in setting project cache")
|
||||||
|
}
|
159
internal/usergrant/repository/eventsourcing/eventstore.go
Normal file
159
internal/usergrant/repository/eventsourcing/eventstore.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/cache/config"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantEventStore struct {
|
||||||
|
es_int.Eventstore
|
||||||
|
userGrantCache *UserGrantCache
|
||||||
|
idGenerator *sonyflake.Sonyflake
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGrantConfig struct {
|
||||||
|
es_int.Eventstore
|
||||||
|
Cache *config.CacheConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartUserGrant(conf UserGrantConfig) (*UserGrantEventStore, error) {
|
||||||
|
userGrantCache, err := StartCache(conf.Cache)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idGenerator := sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
return &UserGrantEventStore{
|
||||||
|
Eventstore: conf.Eventstore,
|
||||||
|
userGrantCache: userGrantCache,
|
||||||
|
idGenerator: idGenerator,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) UserGrantByID(ctx context.Context, id string) (*grant_model.UserGrant, error) {
|
||||||
|
grant := es.userGrantCache.getUserGrant(id)
|
||||||
|
|
||||||
|
query, err := UserGrantByIDQuery(grant.AggregateID, grant.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = es_sdk.Filter(ctx, es.FilterEvents, grant.AppendEvents, query)
|
||||||
|
if err != nil && caos_errs.IsNotFound(err) && grant.Sequence == 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userGrantCache.cacheUserGrant(grant)
|
||||||
|
if grant.State == int32(grant_model.USERGRANTSTATE_REMOVED) {
|
||||||
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-2ks8d", "UserGrant not found")
|
||||||
|
}
|
||||||
|
return model.UserGrantToModel(grant), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) AddUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
|
||||||
|
if grant == nil || !grant.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdiw3", "User grant invalid")
|
||||||
|
}
|
||||||
|
//TODO: Check Uniqueness
|
||||||
|
id, err := es.idGenerator.NextID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
grant.AggregateID = strconv.FormatUint(id, 10)
|
||||||
|
|
||||||
|
repoGrant := model.UserGrantFromModel(grant)
|
||||||
|
|
||||||
|
addAggregate := UserGrantAddedAggregate(es.Eventstore.AggregateCreator(), repoGrant)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoGrant.AppendEvents, addAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return model.UserGrantToModel(repoGrant), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) ChangeUserGrant(ctx context.Context, grant *grant_model.UserGrant) (*grant_model.UserGrant, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo0s9", "invalid grant")
|
||||||
|
}
|
||||||
|
existing, err := es.UserGrantByID(ctx, grant.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repoExisting := model.UserGrantFromModel(existing)
|
||||||
|
repoGrant := model.UserGrantFromModel(grant)
|
||||||
|
|
||||||
|
projectAggregate := UserGrantChangedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userGrantCache.cacheUserGrant(repoExisting)
|
||||||
|
return model.UserGrantToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) RemoveUserGrant(ctx context.Context, grantID string) error {
|
||||||
|
existing, err := es.UserGrantByID(ctx, grantID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
repoExisting := model.UserGrantFromModel(existing)
|
||||||
|
repoGrant := &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: grantID}}
|
||||||
|
projectAggregate := UserGrantRemovedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
es.userGrantCache.cacheUserGrant(repoExisting)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) DeactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
|
||||||
|
if grantID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-8si34", "grantID missing")
|
||||||
|
}
|
||||||
|
existing, err := es.UserGrantByID(ctx, grantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.IsActive() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9sw", "deactivate only possible for active grant")
|
||||||
|
}
|
||||||
|
repoExisting := model.UserGrantFromModel(existing)
|
||||||
|
repoGrant := &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: grantID}}
|
||||||
|
|
||||||
|
projectAggregate := UserGrantDeactivatedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userGrantCache.cacheUserGrant(repoGrant)
|
||||||
|
return model.UserGrantToModel(repoExisting), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *UserGrantEventStore) ReactivateUserGrant(ctx context.Context, grantID string) (*grant_model.UserGrant, error) {
|
||||||
|
if grantID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sksiw", "grantID missing")
|
||||||
|
}
|
||||||
|
existing, err := es.UserGrantByID(ctx, grantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !existing.IsInactive() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-lo9sw", "reactivate only possible for inactive grant")
|
||||||
|
}
|
||||||
|
repoExisting := model.UserGrantFromModel(existing)
|
||||||
|
repoGrant := &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: grantID}}
|
||||||
|
|
||||||
|
projectAggregate := UserGrantReactivatedAggregate(es.Eventstore.AggregateCreator(), repoExisting, repoGrant)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoExisting.AppendEvents, projectAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
es.userGrantCache.cacheUserGrant(repoExisting)
|
||||||
|
return model.UserGrantToModel(repoExisting), nil
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
mock_cache "github.com/caos/zitadel/internal/cache/mock"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/mock"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/sony/sonyflake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetMockedEventstore(ctrl *gomock.Controller, mockEs *mock.MockEventstore) *UserGrantEventStore {
|
||||||
|
return &UserGrantEventStore{
|
||||||
|
Eventstore: mockEs,
|
||||||
|
userGrantCache: GetMockCache(ctrl),
|
||||||
|
idGenerator: GetSonyFlacke(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockCache(ctrl *gomock.Controller) *UserGrantCache {
|
||||||
|
mockCache := mock_cache.NewMockCache(ctrl)
|
||||||
|
mockCache.EXPECT().Get(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||||
|
mockCache.EXPECT().Set(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
|
||||||
|
return &UserGrantCache{userGrantCache: mockCache}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSonyFlacke() *sonyflake.Sonyflake {
|
||||||
|
return sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockUserGrantByIDOK(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
user := model.UserGrant{
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockUserGrantByIDRemoved(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
user := model.UserGrant{
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantAdded, Data: data},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantRemoved},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockUserGrantByIDNoEvents(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
events := []*es_models.Event{}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserGrant(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
user := model.UserGrant{
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantAdded, Data: data},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserGrantInactive(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
user := model.UserGrant{
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(user)
|
||||||
|
events := []*es_models.Event{
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantAdded, Data: data},
|
||||||
|
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.UserGrantDeactivated},
|
||||||
|
}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockManipulateUserGrantNoEvents(ctrl *gomock.Controller) *UserGrantEventStore {
|
||||||
|
events := []*es_models.Event{}
|
||||||
|
mockEs := mock.NewMockEventstore(ctrl)
|
||||||
|
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
|
||||||
|
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
|
||||||
|
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
|
||||||
|
return GetMockedEventstore(ctrl, mockEs)
|
||||||
|
}
|
439
internal/usergrant/repository/eventsourcing/eventstore_test.go
Normal file
439
internal/usergrant/repository/eventsourcing/eventstore_test.go
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserByID(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
grant *model.UserGrant
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user from events, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockUserGrantByIDOK(ctrl),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no events found",
|
||||||
|
args: args{
|
||||||
|
es: GetMockUserGrantByIDNoEvents(ctrl),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no id",
|
||||||
|
args: args{
|
||||||
|
es: GetMockUserGrantByIDOK(ctrl),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removed state",
|
||||||
|
args: args{
|
||||||
|
es: GetMockUserGrantByIDRemoved(ctrl),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.args.es.UserGrantByID(nil, tt.args.grant.AggregateID)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && result.AggregateID != tt.res.grant.AggregateID {
|
||||||
|
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.grant.AggregateID, result.AggregateID)
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddUserGrant(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
result *model.UserGrant
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "add grant, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
UserID: "UserID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
result: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
UserID: "UserID",
|
||||||
|
RoleKeys: []string{"Key"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid grant",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.args.es.AddUserGrant(tt.args.ctx, tt.args.grant)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && result.AggregateID == "" {
|
||||||
|
t.Errorf("result has no id")
|
||||||
|
}
|
||||||
|
if !tt.res.wantErr && result.UserID == "" {
|
||||||
|
t.Errorf("result has no id")
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChangeUserGrant(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
result *model.UserGrant
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "change grant, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
RoleKeys: []string{"KeyChanged"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
result: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
RoleKeys: []string{"KeyChanged"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid grant",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: nil,
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing user not found",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrantNoEvents(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
RoleKeys: []string{"KeyChanged"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.args.es.ChangeUserGrant(tt.args.ctx, tt.args.grant)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && result.AggregateID == "" {
|
||||||
|
t.Errorf("result has no id")
|
||||||
|
}
|
||||||
|
if !tt.res.wantErr && !reflect.DeepEqual(result.RoleKeys, tt.res.result.RoleKeys) {
|
||||||
|
t.Errorf("got wrong result name: expected: %v, actual: %v ", tt.res.result.RoleKeys, result.RoleKeys)
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveUserGrant(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "remove grant, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no grantID",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing grant not found",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrantNoEvents(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := tt.args.es.RemoveUserGrant(tt.args.ctx, tt.args.grant.AggregateID)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && err != nil {
|
||||||
|
t.Errorf("should not get err")
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeactivateUserGrant(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
result *model.UserGrant
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "deactivate, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
result: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
State: model.USERGRANTSTATE_INACTIVE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no grant id",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant not existing",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrantNoEvents(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.args.es.DeactivateUserGrant(tt.args.ctx, tt.args.grant.AggregateID)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && result.AggregateID == "" {
|
||||||
|
t.Errorf("result has no id")
|
||||||
|
}
|
||||||
|
if !tt.res.wantErr && result.ProjectID != tt.res.result.ProjectID {
|
||||||
|
t.Errorf("got wrong result AppID: expected: %v, actual: %v ", tt.res.result.ProjectID, result.ProjectID)
|
||||||
|
}
|
||||||
|
if !tt.res.wantErr && result.State != tt.res.result.State {
|
||||||
|
t.Errorf("got wrong result state: expected: %v, actual: %v ", tt.res.result.State, result.State)
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReactivateUserGrant(t *testing.T) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
type args struct {
|
||||||
|
es *UserGrantEventStore
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
result *model.UserGrant
|
||||||
|
wantErr bool
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reactivate, ok",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrantInactive(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
result: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1},
|
||||||
|
State: model.USERGRANTSTATE_ACTIVE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no grant id",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrant(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant not existing",
|
||||||
|
args: args{
|
||||||
|
es: GetMockManipulateUserGrantNoEvents(ctrl),
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 1}},
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
wantErr: true,
|
||||||
|
errFunc: caos_errs.IsNotFound,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := tt.args.es.ReactivateUserGrant(tt.args.ctx, tt.args.grant.AggregateID)
|
||||||
|
|
||||||
|
if !tt.res.wantErr && result.AggregateID == "" {
|
||||||
|
t.Errorf("result has no id")
|
||||||
|
}
|
||||||
|
if !tt.res.wantErr && result.State != tt.res.result.State {
|
||||||
|
t.Errorf("got wrong result state: expected: %v, actual: %v ", tt.res.result.State, result.State)
|
||||||
|
}
|
||||||
|
if tt.res.wantErr && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
14
internal/usergrant/repository/eventsourcing/model/types.go
Normal file
14
internal/usergrant/repository/eventsourcing/model/types.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserGrantAggregate models.AggregateType = "usergrant"
|
||||||
|
UserGrantUniqueAggregate models.AggregateType = "usergrant-unique"
|
||||||
|
|
||||||
|
UserGrantAdded models.EventType = "user.grant.added"
|
||||||
|
UserGrantChanged models.EventType = "user.grant.changed"
|
||||||
|
UserGrantRemoved models.EventType = "user.grant.removed"
|
||||||
|
UserGrantDeactivated models.EventType = "user.grant.deactivated"
|
||||||
|
UserGrantReactivated models.EventType = "user.grant.reactivated"
|
||||||
|
)
|
@ -0,0 +1,93 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserGrantVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
State int32 `json:"-"`
|
||||||
|
UserID string `json:"userId,omitempty"`
|
||||||
|
ProjectID string `json:"projectId,omitempty"`
|
||||||
|
RoleKeys []string `json:"roleKeys,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGrantID struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
GrantID string `json:"grantId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UserGrant) Changes(changed *UserGrant) map[string]interface{} {
|
||||||
|
changes := make(map[string]interface{}, 1)
|
||||||
|
if !reflect.DeepEqual(g.RoleKeys, changed.RoleKeys) {
|
||||||
|
changes["roleKeys"] = changed.RoleKeys
|
||||||
|
}
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantFromModel(grant *model.UserGrant) *UserGrant {
|
||||||
|
return &UserGrant{
|
||||||
|
ObjectRoot: grant.ObjectRoot,
|
||||||
|
UserID: grant.UserID,
|
||||||
|
ProjectID: grant.ProjectID,
|
||||||
|
State: int32(grant.State),
|
||||||
|
RoleKeys: grant.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantToModel(grant *UserGrant) *model.UserGrant {
|
||||||
|
return &model.UserGrant{
|
||||||
|
ObjectRoot: grant.ObjectRoot,
|
||||||
|
UserID: grant.UserID,
|
||||||
|
ProjectID: grant.ProjectID,
|
||||||
|
State: model.UserGrantState(grant.State),
|
||||||
|
RoleKeys: grant.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UserGrant) AppendEvents(events ...*es_models.Event) error {
|
||||||
|
for _, event := range events {
|
||||||
|
if err := g.AppendEvent(event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UserGrant) AppendEvent(event *es_models.Event) error {
|
||||||
|
g.ObjectRoot.AppendEvent(event)
|
||||||
|
switch event.Type {
|
||||||
|
case UserGrantAdded,
|
||||||
|
UserGrantChanged:
|
||||||
|
return g.setData(event)
|
||||||
|
case UserGrantDeactivated:
|
||||||
|
g.appendGrantStateEvent(model.USERGRANTSTATE_INACTIVE)
|
||||||
|
case UserGrantReactivated:
|
||||||
|
g.appendGrantStateEvent(model.USERGRANTSTATE_ACTIVE)
|
||||||
|
case UserGrantRemoved:
|
||||||
|
g.appendGrantStateEvent(model.USERGRANTSTATE_REMOVED)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UserGrant) appendGrantStateEvent(state model.UserGrantState) {
|
||||||
|
g.State = int32(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UserGrant) setData(event *es_models.Event) error {
|
||||||
|
if err := json.Unmarshal(event.Data, g); err != nil {
|
||||||
|
logging.Log("EVEN-lso9x").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return caos_errs.ThrowInternal(err, "MODEL-o0se3", "could not unmarshal event")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppendGrantStateEvent(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing *UserGrant
|
||||||
|
grant *UserGrantID
|
||||||
|
event *es_models.Event
|
||||||
|
state model.UserGrantState
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
result *UserGrant
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "append deactivate grant event",
|
||||||
|
args: args{
|
||||||
|
existing: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}},
|
||||||
|
grant: &UserGrantID{GrantID: "GrantID"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
state: model.USERGRANTSTATE_INACTIVE,
|
||||||
|
},
|
||||||
|
result: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}, State: int32(model.USERGRANTSTATE_INACTIVE)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append reactivate grant event",
|
||||||
|
args: args{
|
||||||
|
existing: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}},
|
||||||
|
grant: &UserGrantID{GrantID: "GrantID"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
state: model.USERGRANTSTATE_ACTIVE,
|
||||||
|
},
|
||||||
|
result: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}, State: int32(model.USERGRANTSTATE_ACTIVE)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append remove grant event",
|
||||||
|
args: args{
|
||||||
|
existing: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}},
|
||||||
|
grant: &UserGrantID{GrantID: "GrantID"},
|
||||||
|
event: &es_models.Event{},
|
||||||
|
state: model.USERGRANTSTATE_REMOVED,
|
||||||
|
},
|
||||||
|
result: &UserGrant{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID", RoleKeys: []string{"Key"}, State: int32(model.USERGRANTSTATE_REMOVED)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.args.existing.appendGrantStateEvent(tt.args.state)
|
||||||
|
if tt.args.existing.State != tt.result.State {
|
||||||
|
t.Errorf("got wrong result: actual: %v, expected: %v ", tt.result.State, tt.args.existing.State)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
92
internal/usergrant/repository/eventsourcing/user_grant.go
Normal file
92
internal/usergrant/repository/eventsourcing/user_grant.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserGrantByIDQuery(id string, latestSequence uint64) (*es_models.SearchQuery, error) {
|
||||||
|
if id == "" {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ols34", "id should be filled")
|
||||||
|
}
|
||||||
|
return UserGrantQuery(latestSequence).
|
||||||
|
AggregateIDFilter(id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantQuery(latestSequence uint64) *es_models.SearchQuery {
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.UserGrantAggregate).
|
||||||
|
LatestSequenceFilter(latestSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, grant *model.UserGrant) (*es_models.Aggregate, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-dis83", "existing grant should not be nil")
|
||||||
|
}
|
||||||
|
return aggCreator.NewAggregate(ctx, grant.AggregateID, model.UserGrantAggregate, model.UserGrantVersion, grant.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantAddedAggregate(aggCreator *es_models.AggregateCreator, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := UserGrantAggregate(ctx, aggCreator, grant)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserGrantAdded, grant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-osl8x", "grant should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserGrantAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changes := existing.Changes(grant)
|
||||||
|
return agg.AppendEvent(model.UserGrantChanged, changes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantDeactivatedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-lo21s", "grant should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserGrantAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserGrantDeactivated, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantReactivatedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-mks34", "grant should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserGrantAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserGrantReactivated, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.UserGrant, grant *model.UserGrant) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
if grant == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-lo21s", "grant should not be nil")
|
||||||
|
}
|
||||||
|
agg, err := UserGrantAggregate(ctx, aggCreator, existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return agg.AppendEvent(model.UserGrantRemoved, nil)
|
||||||
|
}
|
||||||
|
}
|
405
internal/usergrant/repository/eventsourcing/user_grant_test.go
Normal file
405
internal/usergrant/repository/eventsourcing/user_grant_test.go
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserGrantAddedAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
grant *model.UserGrant
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventType models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "usergrant added ok",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}, UserID: "UserID", ProjectID: "ProjectID"},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
eventType: model.UserGrantAdded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
grant: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := UserGrantAddedAggregate(tt.args.aggCreator, tt.args.grant)(tt.args.ctx)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
if tt.res.errFunc == nil && agg.Events[0].Type != tt.res.eventType {
|
||||||
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventType, agg.Events[0].Type.String())
|
||||||
|
}
|
||||||
|
if tt.res.errFunc == nil && agg.Events[0].Data == nil {
|
||||||
|
t.Errorf("should have data in event")
|
||||||
|
}
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGrantChangedAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
existing *model.UserGrant
|
||||||
|
new *model.UserGrant
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventTypes []models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "change project grant",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"}},
|
||||||
|
new: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"KeyChanged"},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
eventTypes: []models.EventType{model.UserGrantChanged},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: nil,
|
||||||
|
new: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"}},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"}},
|
||||||
|
new: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := UserGrantChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
for i := 0; i < tt.res.eventLen; i++ {
|
||||||
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||||
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||||
|
}
|
||||||
|
if tt.res.errFunc == nil && agg.Events[i].Data == nil {
|
||||||
|
t.Errorf("should have data in event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGrantRemovedAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
existing *model.UserGrant
|
||||||
|
new *model.UserGrant
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventTypes []models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "remove app",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"}},
|
||||||
|
new: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
eventTypes: []models.EventType{model.UserGrantRemoved},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing project nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
UserID: "UserID",
|
||||||
|
ProjectID: "ProjectID",
|
||||||
|
RoleKeys: []string{"Key"}},
|
||||||
|
new: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := UserGrantRemovedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
for i := 0; i < tt.res.eventLen; i++ {
|
||||||
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||||
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGrantDeactivatedAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
existing *model.UserGrant
|
||||||
|
new *model.UserGrant
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventTypes []models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "deactivate project grant",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
},
|
||||||
|
new: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
eventTypes: []models.EventType{model.UserGrantDeactivated},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||||
|
new: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := UserGrantDeactivatedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
for i := 0; i < tt.res.eventLen; i++ {
|
||||||
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||||
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserGrantReactivatedAggregate(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
existing *model.UserGrant
|
||||||
|
new *model.UserGrant
|
||||||
|
aggCreator *models.AggregateCreator
|
||||||
|
}
|
||||||
|
type res struct {
|
||||||
|
eventLen int
|
||||||
|
eventTypes []models.EventType
|
||||||
|
errFunc func(err error) bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
res res
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reactivate project grant",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
},
|
||||||
|
new: &model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "ID"},
|
||||||
|
},
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
eventLen: 1,
|
||||||
|
eventTypes: []models.EventType{model.UserGrantReactivated},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "existing grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "grant nil",
|
||||||
|
args: args{
|
||||||
|
ctx: auth.NewMockContext("orgID", "userID"),
|
||||||
|
existing: &model.UserGrant{ObjectRoot: models.ObjectRoot{AggregateID: "ID"}},
|
||||||
|
new: nil,
|
||||||
|
aggCreator: models.NewAggregateCreator("Test"),
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
errFunc: caos_errs.IsPreconditionFailed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
agg, err := UserGrantReactivatedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
|
||||||
|
|
||||||
|
if tt.res.errFunc == nil && len(agg.Events) != tt.res.eventLen {
|
||||||
|
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
|
||||||
|
}
|
||||||
|
for i := 0; i < tt.res.eventLen; i++ {
|
||||||
|
if tt.res.errFunc == nil && agg.Events[i].Type != tt.res.eventTypes[i] {
|
||||||
|
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.res.errFunc != nil && !tt.res.errFunc(err) {
|
||||||
|
t.Errorf("got wrong err: %v ", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -470,6 +470,11 @@ var ManagementService_AuthMethods = utils_auth.MethodMapping{
|
|||||||
CheckParam: "",
|
CheckParam: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"/caos.zitadel.management.api.v1.ManagementService/RemoveUserGrant": utils_auth.Option{
|
||||||
|
Permission: "user.grant.delete",
|
||||||
|
CheckParam: "",
|
||||||
|
},
|
||||||
|
|
||||||
"/caos.zitadel.management.api.v1.ManagementService/SearchProjectUserGrants": utils_auth.Option{
|
"/caos.zitadel.management.api.v1.ManagementService/SearchProjectUserGrants": utils_auth.Option{
|
||||||
Permission: "project.user.grant.read",
|
Permission: "project.user.grant.read",
|
||||||
CheckParam: "ProjectId",
|
CheckParam: "ProjectId",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3039,6 +3039,44 @@ func request_ManagementService_ReactivateUserGrant_0(ctx context.Context, marsha
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func request_ManagementService_RemoveUserGrant_0(ctx context.Context, marshaler runtime.Marshaler, client ManagementServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq UserGrantID
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["user_id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user_id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.UserId, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user_id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok = pathParams["id"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Id, err = runtime.String(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.RemoveUserGrant(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func request_ManagementService_SearchProjectUserGrants_0(ctx context.Context, marshaler runtime.Marshaler, client ManagementServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_ManagementService_SearchProjectUserGrants_0(ctx context.Context, marshaler runtime.Marshaler, client ManagementServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq ProjectUserGrantSearchRequest
|
var protoReq ProjectUserGrantSearchRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
@ -5576,6 +5614,26 @@ func RegisterManagementServiceHandlerClient(ctx context.Context, mux *runtime.Se
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("DELETE", pattern_ManagementService_RemoveUserGrant_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
|
defer cancel()
|
||||||
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
|
rctx, err := runtime.AnnotateContext(ctx, mux, req)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_ManagementService_RemoveUserGrant_0(rctx, inboundMarshaler, client, req, pathParams)
|
||||||
|
ctx = runtime.NewServerMetadataContext(ctx, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_ManagementService_RemoveUserGrant_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ManagementService_SearchProjectUserGrants_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_ManagementService_SearchProjectUserGrants_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -6028,6 +6086,8 @@ var (
|
|||||||
|
|
||||||
pattern_ManagementService_ReactivateUserGrant_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"users", "user_id", "grants", "id", "_reactivate"}, ""))
|
pattern_ManagementService_ReactivateUserGrant_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"users", "user_id", "grants", "id", "_reactivate"}, ""))
|
||||||
|
|
||||||
|
pattern_ManagementService_RemoveUserGrant_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"users", "user_id", "grants", "id"}, ""))
|
||||||
|
|
||||||
pattern_ManagementService_SearchProjectUserGrants_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3, 2, 4}, []string{"projects", "project_id", "users", "grants", "_search"}, ""))
|
pattern_ManagementService_SearchProjectUserGrants_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 2, 3, 2, 4}, []string{"projects", "project_id", "users", "grants", "_search"}, ""))
|
||||||
|
|
||||||
pattern_ManagementService_ProjectUserGrantByID_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"projects", "project_id", "users", "user_id", "grants", "id"}, ""))
|
pattern_ManagementService_ProjectUserGrantByID_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"projects", "project_id", "users", "user_id", "grants", "id"}, ""))
|
||||||
@ -6244,6 +6304,8 @@ var (
|
|||||||
|
|
||||||
forward_ManagementService_ReactivateUserGrant_0 = runtime.ForwardResponseMessage
|
forward_ManagementService_ReactivateUserGrant_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_ManagementService_RemoveUserGrant_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ManagementService_SearchProjectUserGrants_0 = runtime.ForwardResponseMessage
|
forward_ManagementService_SearchProjectUserGrants_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ManagementService_ProjectUserGrantByID_0 = runtime.ForwardResponseMessage
|
forward_ManagementService_ProjectUserGrantByID_0 = runtime.ForwardResponseMessage
|
||||||
|
@ -3185,6 +3185,34 @@
|
|||||||
"ManagementService"
|
"ManagementService"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"delete": {
|
||||||
|
"operationId": "RemoveUserGrant",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ManagementService"
|
||||||
|
]
|
||||||
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"operationId": "UpdateUserGrant",
|
"operationId": "UpdateUserGrant",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -5477,6 +5505,14 @@
|
|||||||
"sequence": {
|
"sequence": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"creation_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"change_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5496,6 +5532,14 @@
|
|||||||
"sequence": {
|
"sequence": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"creation_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"change_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5564,9 +5608,6 @@
|
|||||||
"user_id": {
|
"user_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"org_id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"project_id": {
|
"project_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -5705,6 +5746,14 @@
|
|||||||
"sequence": {
|
"sequence": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"creation_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"change_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -5714,9 +5763,6 @@
|
|||||||
"id": {
|
"id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"state": {
|
|
||||||
"$ref": "#/definitions/v1UserState"
|
|
||||||
},
|
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -5741,6 +5787,14 @@
|
|||||||
"sequence": {
|
"sequence": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "uint64"
|
"format": "uint64"
|
||||||
|
},
|
||||||
|
"creation_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"change_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1537,6 +1537,26 @@ func (mr *MockManagementServiceClientMockRecorder) RemoveProjectRole(arg0, arg1
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProjectRole", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveProjectRole), varargs...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveProjectRole", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveProjectRole), varargs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveUserGrant mocks base method
|
||||||
|
func (m *MockManagementServiceClient) RemoveUserGrant(arg0 context.Context, arg1 *grpc.UserGrantID, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
varargs := []interface{}{arg0, arg1}
|
||||||
|
for _, a := range arg2 {
|
||||||
|
varargs = append(varargs, a)
|
||||||
|
}
|
||||||
|
ret := m.ctrl.Call(m, "RemoveUserGrant", varargs...)
|
||||||
|
ret0, _ := ret[0].(*emptypb.Empty)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveUserGrant indicates an expected call of RemoveUserGrant
|
||||||
|
func (mr *MockManagementServiceClientMockRecorder) RemoveUserGrant(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
varargs := append([]interface{}{arg0, arg1}, arg2...)
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveUserGrant", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveUserGrant), varargs...)
|
||||||
|
}
|
||||||
|
|
||||||
// ResendEmailVerificationMail mocks base method
|
// ResendEmailVerificationMail mocks base method
|
||||||
func (m *MockManagementServiceClient) ResendEmailVerificationMail(arg0 context.Context, arg1 *grpc.UserID, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
func (m *MockManagementServiceClient) ResendEmailVerificationMail(arg0 context.Context, arg1 *grpc.UserID, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -13,18 +13,22 @@ import (
|
|||||||
var _ ManagementServiceServer = (*Server)(nil)
|
var _ ManagementServiceServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
port string
|
port string
|
||||||
project repository.ProjectRepository
|
project repository.ProjectRepository
|
||||||
verifier *mgmt_auth.TokenVerifier
|
user repository.UserRepository
|
||||||
authZ auth.Config
|
usergrant repository.UserGrantRepository
|
||||||
|
verifier *mgmt_auth.TokenVerifier
|
||||||
|
authZ auth.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartServer(conf grpc_util.ServerConfig, authZ auth.Config, repo repository.Repository) *Server {
|
func StartServer(conf grpc_util.ServerConfig, authZ auth.Config, repo repository.Repository) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
port: conf.Port,
|
port: conf.Port,
|
||||||
project: repo,
|
project: repo,
|
||||||
authZ: authZ,
|
user: repo,
|
||||||
verifier: mgmt_auth.Start(),
|
usergrant: repo,
|
||||||
|
authZ: authZ,
|
||||||
|
verifier: mgmt_auth.Start(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,12 @@ import (
|
|||||||
"github.com/golang/protobuf/ptypes/empty"
|
"github.com/golang/protobuf/ptypes/empty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) GetUserByID(ctx context.Context, userID *UserID) (*User, error) {
|
func (s *Server) GetUserByID(ctx context.Context, id *UserID) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-0oVbs", "Not implemented")
|
user, err := s.user.UserByID(ctx, id.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserByEmailGlobal(ctx context.Context, email *UserEmailID) (*User, error) {
|
func (s *Server) GetUserByEmailGlobal(ctx context.Context, email *UserEmailID) (*User, error) {
|
||||||
@ -26,76 +30,132 @@ func (s *Server) IsUserUnique(ctx context.Context, request *UniqueUserRequest) (
|
|||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-olF56", "Not implemented")
|
return nil, errors.ThrowUnimplemented(nil, "GRPC-olF56", "Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreateUser(ctx context.Context, request *CreateUserRequest) (*User, error) {
|
func (s *Server) CreateUser(ctx context.Context, in *CreateUserRequest) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-sd4fs", "Not implemented")
|
user, err := s.user.CreateUser(ctx, userCreateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeactivateUser(ctx context.Context, ID *UserID) (*User, error) {
|
func (s *Server) DeactivateUser(ctx context.Context, in *UserID) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-Vgh64", "Not implemented")
|
user, err := s.user.DeactivateUser(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ReactivateUser(ctx context.Context, ID *UserID) (*User, error) {
|
func (s *Server) ReactivateUser(ctx context.Context, in *UserID) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-mCx4f", "Not implemented")
|
user, err := s.user.ReactivateUser(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) LockUser(ctx context.Context, ID *UserID) (*User, error) {
|
func (s *Server) LockUser(ctx context.Context, in *UserID) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-ds4fd", "Not implemented")
|
user, err := s.user.LockUser(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UnlockUser(ctx context.Context, ID *UserID) (*User, error) {
|
func (s *Server) UnlockUser(ctx context.Context, in *UserID) (*User, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-MV7dd", "Not implemented")
|
user, err := s.user.UnlockUser(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return userFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeleteUser(ctx context.Context, ID *UserID) (*empty.Empty, error) {
|
func (s *Server) DeleteUser(ctx context.Context, in *UserID) (*empty.Empty, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-as4fg", "Not implemented")
|
return nil, errors.ThrowUnimplemented(nil, "GRPC-as4fg", "Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserProfile(ctx context.Context, ID *UserID) (*UserProfile, error) {
|
func (s *Server) GetUserProfile(ctx context.Context, in *UserID) (*UserProfile, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-mT67d", "Not implemented")
|
profile, err := s.user.ProfileByID(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileFromModel(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateUserProfile(ctx context.Context, request *UpdateUserProfileRequest) (*UserProfile, error) {
|
func (s *Server) UpdateUserProfile(ctx context.Context, request *UpdateUserProfileRequest) (*UserProfile, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-asje3", "Not implemented")
|
profile, err := s.user.ChangeProfile(ctx, updateProfileToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return profileFromModel(profile), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserEmail(ctx context.Context, ID *UserID) (*UserEmail, error) {
|
func (s *Server) GetUserEmail(ctx context.Context, in *UserID) (*UserEmail, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-peo9d", "Not implemented")
|
email, err := s.user.EmailByID(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailFromModel(email), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ChangeUserEmail(ctx context.Context, request *UpdateUserEmailRequest) (*UserEmail, error) {
|
func (s *Server) ChangeUserEmail(ctx context.Context, request *UpdateUserEmailRequest) (*UserEmail, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-cloeS", "Not implemented")
|
email, err := s.user.ChangeEmail(ctx, updateEmailToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailFromModel(email), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ResendEmailVerificationMail(ctx context.Context, ID *UserID) (*empty.Empty, error) {
|
func (s *Server) ResendEmailVerificationMail(ctx context.Context, in *UserID) (*empty.Empty, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-dwsP9", "Not implemented")
|
err := s.user.CreateEmailVerificationCode(ctx, in.Id)
|
||||||
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserPhone(ctx context.Context, ID *UserID) (*UserPhone, error) {
|
func (s *Server) GetUserPhone(ctx context.Context, in *UserID) (*UserPhone, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-wlf7f", "Not implemented")
|
phone, err := s.user.PhoneByID(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return phoneFromModel(phone), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ChangeUserPhone(ctx context.Context, request *UpdateUserPhoneRequest) (*UserPhone, error) {
|
func (s *Server) ChangeUserPhone(ctx context.Context, request *UpdateUserPhoneRequest) (*UserPhone, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-pld5g", "Not implemented")
|
phone, err := s.user.ChangePhone(ctx, updatePhoneToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return phoneFromModel(phone), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ResendPhoneVerificationCode(ctx context.Context, ID *UserID) (*empty.Empty, error) {
|
func (s *Server) ResendPhoneVerificationCode(ctx context.Context, in *UserID) (*empty.Empty, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-98hdE", "Not implemented")
|
err := s.user.CreatePhoneVerificationCode(ctx, in.Id)
|
||||||
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserAddress(ctx context.Context, ID *UserID) (*UserAddress, error) {
|
func (s *Server) GetUserAddress(ctx context.Context, in *UserID) (*UserAddress, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-plt67", "Not implemented")
|
address, err := s.user.AddressByID(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addressFromModel(address), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateUserAddress(ctx context.Context, request *UpdateUserAddressRequest) (*UserAddress, error) {
|
func (s *Server) UpdateUserAddress(ctx context.Context, request *UpdateUserAddressRequest) (*UserAddress, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-dleo3", "Not implemented")
|
address, err := s.user.ChangeAddress(ctx, updateAddressToModel(request))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addressFromModel(address), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SendSetPasswordNotification(ctx context.Context, request *SetPasswordNotificationRequest) (*empty.Empty, error) {
|
func (s *Server) SendSetPasswordNotification(ctx context.Context, request *SetPasswordNotificationRequest) (*empty.Empty, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-LSe7s", "Not implemented")
|
err := s.user.RequestSetPassword(ctx, request.Id, notifyTypeToModel(request.Type))
|
||||||
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SetInitialPassword(ctx context.Context, request *PasswordRequest) (*empty.Empty, error) {
|
func (s *Server) SetInitialPassword(ctx context.Context, request *PasswordRequest) (*empty.Empty, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-ldo3s", "Not implemented")
|
_, err := s.user.SetOneTimePassword(ctx, passwordRequestToModel(request))
|
||||||
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserMfas(ctx context.Context, userID *UserID) (*MultiFactors, error) {
|
func (s *Server) GetUserMfas(ctx context.Context, userID *UserID) (*MultiFactors, error) {
|
||||||
|
258
pkg/management/api/grpc/user_converter.go
Normal file
258
pkg/management/api/grpc/user_converter.go
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
func userFromModel(user *usr_model.User) *User {
|
||||||
|
creationDate, err := ptypes.TimestampProto(user.CreationDate)
|
||||||
|
logging.Log("GRPC-8duwe").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(user.ChangeDate)
|
||||||
|
logging.Log("GRPC-ckoe3d").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
converted := &User{
|
||||||
|
Id: user.AggregateID,
|
||||||
|
State: userStateFromModel(user.State),
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: user.Sequence,
|
||||||
|
UserName: user.UserName,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
DisplayName: user.DisplayName,
|
||||||
|
NickName: user.NickName,
|
||||||
|
PreferredLanguage: user.PreferredLanguage.String(),
|
||||||
|
Gender: genderFromModel(user.Gender),
|
||||||
|
}
|
||||||
|
if user.Email != nil {
|
||||||
|
converted.Email = user.EmailAddress
|
||||||
|
converted.IsEmailVerified = user.IsEmailVerified
|
||||||
|
}
|
||||||
|
if user.Phone != nil {
|
||||||
|
converted.Phone = user.PhoneNumber
|
||||||
|
converted.IsPhoneVerified = user.IsPhoneVerified
|
||||||
|
}
|
||||||
|
if user.Address != nil {
|
||||||
|
converted.Country = user.Country
|
||||||
|
converted.Locality = user.Locality
|
||||||
|
converted.PostalCode = user.PostalCode
|
||||||
|
converted.Region = user.Region
|
||||||
|
converted.StreetAddress = user.StreetAddress
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func userCreateToModel(u *CreateUserRequest) *usr_model.User {
|
||||||
|
preferredLanguage, err := language.Parse(u.PreferredLanguage)
|
||||||
|
logging.Log("GRPC-cK5k2").OnError(err).Debug("language malformed")
|
||||||
|
|
||||||
|
user := &usr_model.User{
|
||||||
|
Profile: &usr_model.Profile{
|
||||||
|
UserName: u.UserName,
|
||||||
|
FirstName: u.FirstName,
|
||||||
|
LastName: u.LastName,
|
||||||
|
NickName: u.NickName,
|
||||||
|
DisplayName: u.DisplayName,
|
||||||
|
PreferredLanguage: preferredLanguage,
|
||||||
|
Gender: genderToModel(u.Gender),
|
||||||
|
},
|
||||||
|
Email: &usr_model.Email{
|
||||||
|
EmailAddress: u.Email,
|
||||||
|
IsEmailVerified: u.IsEmailVerified,
|
||||||
|
},
|
||||||
|
Address: &usr_model.Address{
|
||||||
|
Country: u.Country,
|
||||||
|
Locality: u.Locality,
|
||||||
|
PostalCode: u.PostalCode,
|
||||||
|
Region: u.Region,
|
||||||
|
StreetAddress: u.StreetAddress,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if u.Password != "" {
|
||||||
|
user.Password = &usr_model.Password{SecretString: u.Password}
|
||||||
|
}
|
||||||
|
if u.Phone != "" {
|
||||||
|
user.Phone = &usr_model.Phone{PhoneNumber: u.Phone, IsPhoneVerified: u.IsPhoneVerified}
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func passwordRequestToModel(r *PasswordRequest) *usr_model.Password {
|
||||||
|
return &usr_model.Password{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: r.Id},
|
||||||
|
SecretString: r.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileFromModel(profile *usr_model.Profile) *UserProfile {
|
||||||
|
creationDate, err := ptypes.TimestampProto(profile.CreationDate)
|
||||||
|
logging.Log("GRPC-dkso3").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(profile.ChangeDate)
|
||||||
|
logging.Log("GRPC-ski8d").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &UserProfile{
|
||||||
|
Id: profile.AggregateID,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: profile.Sequence,
|
||||||
|
UserName: profile.UserName,
|
||||||
|
FirstName: profile.FirstName,
|
||||||
|
LastName: profile.LastName,
|
||||||
|
DisplayName: profile.DisplayName,
|
||||||
|
NickName: profile.NickName,
|
||||||
|
PreferredLanguage: profile.PreferredLanguage.String(),
|
||||||
|
Gender: genderFromModel(profile.Gender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateProfileToModel(u *UpdateUserProfileRequest) *usr_model.Profile {
|
||||||
|
preferredLanguage, err := language.Parse(u.PreferredLanguage)
|
||||||
|
logging.Log("GRPC-d8k2s").OnError(err).Debug("language malformed")
|
||||||
|
|
||||||
|
return &usr_model.Profile{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: u.Id},
|
||||||
|
FirstName: u.FirstName,
|
||||||
|
LastName: u.LastName,
|
||||||
|
NickName: u.NickName,
|
||||||
|
DisplayName: u.DisplayName,
|
||||||
|
PreferredLanguage: preferredLanguage,
|
||||||
|
Gender: genderToModel(u.Gender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func emailFromModel(email *usr_model.Email) *UserEmail {
|
||||||
|
creationDate, err := ptypes.TimestampProto(email.CreationDate)
|
||||||
|
logging.Log("GRPC-d9ow2").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(email.ChangeDate)
|
||||||
|
logging.Log("GRPC-s0dkw").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &UserEmail{
|
||||||
|
Id: email.AggregateID,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: email.Sequence,
|
||||||
|
Email: email.EmailAddress,
|
||||||
|
IsEmailVerified: email.IsEmailVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateEmailToModel(e *UpdateUserEmailRequest) *usr_model.Email {
|
||||||
|
return &usr_model.Email{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: e.Id},
|
||||||
|
EmailAddress: e.Email,
|
||||||
|
IsEmailVerified: e.IsEmailVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func phoneFromModel(phone *usr_model.Phone) *UserPhone {
|
||||||
|
creationDate, err := ptypes.TimestampProto(phone.CreationDate)
|
||||||
|
logging.Log("GRPC-ps9ws").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(phone.ChangeDate)
|
||||||
|
logging.Log("GRPC-09ewq").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &UserPhone{
|
||||||
|
Id: phone.AggregateID,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: phone.Sequence,
|
||||||
|
Phone: phone.PhoneNumber,
|
||||||
|
IsPhoneVerified: phone.IsPhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePhoneToModel(e *UpdateUserPhoneRequest) *usr_model.Phone {
|
||||||
|
return &usr_model.Phone{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: e.Id},
|
||||||
|
PhoneNumber: e.Phone,
|
||||||
|
IsPhoneVerified: e.IsPhoneVerified,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressFromModel(address *usr_model.Address) *UserAddress {
|
||||||
|
creationDate, err := ptypes.TimestampProto(address.CreationDate)
|
||||||
|
logging.Log("GRPC-ud8w7").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(address.ChangeDate)
|
||||||
|
logging.Log("GRPC-si9ws").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &UserAddress{
|
||||||
|
Id: address.AggregateID,
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: address.Sequence,
|
||||||
|
Country: address.Country,
|
||||||
|
StreetAddress: address.StreetAddress,
|
||||||
|
Region: address.Region,
|
||||||
|
PostalCode: address.PostalCode,
|
||||||
|
Locality: address.Locality,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAddressToModel(address *UpdateUserAddressRequest) *usr_model.Address {
|
||||||
|
return &usr_model.Address{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: address.Id},
|
||||||
|
Country: address.Country,
|
||||||
|
StreetAddress: address.StreetAddress,
|
||||||
|
Region: address.Region,
|
||||||
|
PostalCode: address.PostalCode,
|
||||||
|
Locality: address.Locality,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyTypeToModel(state NotificationType) usr_model.NotificationType {
|
||||||
|
switch state {
|
||||||
|
case NotificationType_NOTIFICATIONTYPE_EMAIL:
|
||||||
|
return usr_model.NOTIFICATIONTYPE_EMAIL
|
||||||
|
case NotificationType_NOTIFICATIONTYPE_SMS:
|
||||||
|
return usr_model.NOTIFICATIONTYPE_SMS
|
||||||
|
default:
|
||||||
|
return usr_model.NOTIFICATIONTYPE_EMAIL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userStateFromModel(state usr_model.UserState) UserState {
|
||||||
|
switch state {
|
||||||
|
case usr_model.USERSTATE_ACTIVE:
|
||||||
|
return UserState_USERSTATE_ACTIVE
|
||||||
|
case usr_model.USERSTATE_INACTIVE:
|
||||||
|
return UserState_USERSTATE_INACTIVE
|
||||||
|
case usr_model.USERSTATE_LOCKED:
|
||||||
|
return UserState_USERSTATE_LOCKED
|
||||||
|
default:
|
||||||
|
return UserState_USERSTATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genderFromModel(gender usr_model.Gender) Gender {
|
||||||
|
switch gender {
|
||||||
|
case usr_model.GENDER_FEMALE:
|
||||||
|
return Gender_GENDER_FEMALE
|
||||||
|
case usr_model.GENDER_MALE:
|
||||||
|
return Gender_GENDER_MALE
|
||||||
|
case usr_model.GENDER_DIVERSE:
|
||||||
|
return Gender_GENDER_DIVERSE
|
||||||
|
default:
|
||||||
|
return Gender_GENDER_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genderToModel(gender Gender) usr_model.Gender {
|
||||||
|
switch gender {
|
||||||
|
case Gender_GENDER_FEMALE:
|
||||||
|
return usr_model.GENDER_FEMALE
|
||||||
|
case Gender_GENDER_MALE:
|
||||||
|
return usr_model.GENDER_MALE
|
||||||
|
case Gender_GENDER_DIVERSE:
|
||||||
|
return usr_model.GENDER_DIVERSE
|
||||||
|
default:
|
||||||
|
return usr_model.GENDER_UNDEFINED
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package grpc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/golang/protobuf/ptypes/empty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) SearchUserGrants(ctx context.Context, request *UserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
func (s *Server) SearchUserGrants(ctx context.Context, request *UserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
||||||
@ -10,20 +11,45 @@ func (s *Server) SearchUserGrants(ctx context.Context, request *UserGrantSearchR
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UserGrantByID(ctx context.Context, request *UserGrantID) (*UserGrant, error) {
|
func (s *Server) UserGrantByID(ctx context.Context, request *UserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-9dksF", "Not implemented")
|
user, err := s.usergrant.UserGrantByID(ctx, request.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreateUserGrant(ctx context.Context, in *UserGrantCreate) (*UserGrant, error) {
|
func (s *Server) CreateUserGrant(ctx context.Context, in *UserGrantCreate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-2kdl2", "Not implemented")
|
user, err := s.usergrant.AddUserGrant(ctx, userGrantCreateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
func (s *Server) UpdateUserGrant(ctx context.Context, in *UserGrantUpdate) (*UserGrant, error) {
|
func (s *Server) UpdateUserGrant(ctx context.Context, in *UserGrantUpdate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-83jsF", "Not implemented")
|
user, err := s.usergrant.ChangeUserGrant(ctx, userGrantUpdateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
func (s *Server) DeactivateUserGrant(ctx context.Context, in *UserGrantID) (*UserGrant, error) {
|
func (s *Server) DeactivateUserGrant(ctx context.Context, in *UserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-93dj3", "Not implemented")
|
user, err := s.usergrant.DeactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
func (s *Server) ReactivateUserGrant(ctx context.Context, in *UserGrantID) (*UserGrant, error) {
|
func (s *Server) ReactivateUserGrant(ctx context.Context, in *UserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-2kSfs", "Not implemented")
|
user, err := s.usergrant.ReactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RemoveUserGrant(ctx context.Context, in *UserGrantID) (*empty.Empty, error) {
|
||||||
|
err := s.usergrant.RemoveUserGrant(ctx, in.Id)
|
||||||
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SearchProjectUserGrants(ctx context.Context, request *ProjectUserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
func (s *Server) SearchProjectUserGrants(ctx context.Context, request *ProjectUserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
||||||
@ -31,22 +57,42 @@ func (s *Server) SearchProjectUserGrants(ctx context.Context, request *ProjectUs
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ProjectUserGrantByID(ctx context.Context, request *ProjectUserGrantID) (*UserGrant, error) {
|
func (s *Server) ProjectUserGrantByID(ctx context.Context, request *ProjectUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-dk32s", "Not implemented")
|
user, err := s.usergrant.UserGrantByID(ctx, request.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreateProjectUserGrant(ctx context.Context, in *UserGrantCreate) (*UserGrant, error) {
|
func (s *Server) CreateProjectUserGrant(ctx context.Context, in *UserGrantCreate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-0or5G", "Not implemented")
|
user, err := s.usergrant.AddUserGrant(ctx, userGrantCreateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
func (s *Server) UpdateProjectUserGrant(ctx context.Context, in *ProjectUserGrantUpdate) (*UserGrant, error) {
|
func (s *Server) UpdateProjectUserGrant(ctx context.Context, in *ProjectUserGrantUpdate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-asl4D", "Not implemented")
|
user, err := s.usergrant.ChangeUserGrant(ctx, projectUserGrantUpdateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeactivateProjectUserGrant(ctx context.Context, in *ProjectUserGrantID) (*UserGrant, error) {
|
func (s *Server) DeactivateProjectUserGrant(ctx context.Context, in *ProjectUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-2fG6h", "Not implemented")
|
user, err := s.usergrant.DeactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ReactivateProjectUserGrant(ctx context.Context, in *ProjectUserGrantID) (*UserGrant, error) {
|
func (s *Server) ReactivateProjectUserGrant(ctx context.Context, in *ProjectUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-03kSc", "Not implemented")
|
user, err := s.usergrant.ReactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) SearchProjectGrantUserGrants(ctx context.Context, request *ProjectGrantUserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
func (s *Server) SearchProjectGrantUserGrants(ctx context.Context, request *ProjectGrantUserGrantSearchRequest) (*UserGrantSearchResponse, error) {
|
||||||
@ -54,20 +100,40 @@ func (s *Server) SearchProjectGrantUserGrants(ctx context.Context, request *Proj
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ProjectGrantUserGrantByID(ctx context.Context, request *ProjectGrantUserGrantID) (*UserGrant, error) {
|
func (s *Server) ProjectGrantUserGrantByID(ctx context.Context, request *ProjectGrantUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-9kfSc", "Not implemented")
|
user, err := s.usergrant.UserGrantByID(ctx, request.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) CreateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantCreate) (*UserGrant, error) {
|
func (s *Server) CreateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantCreate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-293md", "Not implemented")
|
user, err := s.usergrant.ChangeUserGrant(ctx, projectGrantUserGrantCreateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
func (s *Server) UpdateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantUpdate) (*UserGrant, error) {
|
func (s *Server) UpdateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantUpdate) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-76fGe", "Not implemented")
|
user, err := s.usergrant.ChangeUserGrant(ctx, projectGrantUserGrantUpdateToModel(in))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) DeactivateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantID) (*UserGrant, error) {
|
func (s *Server) DeactivateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-sFsi3", "Not implemented")
|
user, err := s.usergrant.DeactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ReactivateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantID) (*UserGrant, error) {
|
func (s *Server) ReactivateProjectGrantUserGrant(ctx context.Context, in *ProjectGrantUserGrantID) (*UserGrant, error) {
|
||||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-ckr56", "Not implemented")
|
user, err := s.usergrant.ReactivateUserGrant(ctx, in.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return usergrantFromModel(user), nil
|
||||||
}
|
}
|
||||||
|
76
pkg/management/api/grpc/user_grant_converter.go
Normal file
76
pkg/management/api/grpc/user_grant_converter.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func usergrantFromModel(grant *grant_model.UserGrant) *UserGrant {
|
||||||
|
creationDate, err := ptypes.TimestampProto(grant.CreationDate)
|
||||||
|
logging.Log("GRPC-ki9ds").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
changeDate, err := ptypes.TimestampProto(grant.ChangeDate)
|
||||||
|
logging.Log("GRPC-sl9ew").OnError(err).Debug("unable to parse timestamp")
|
||||||
|
|
||||||
|
return &UserGrant{
|
||||||
|
Id: grant.AggregateID,
|
||||||
|
UserId: grant.UserID,
|
||||||
|
State: usergrantStateFromModel(grant.State),
|
||||||
|
CreationDate: creationDate,
|
||||||
|
ChangeDate: changeDate,
|
||||||
|
Sequence: grant.Sequence,
|
||||||
|
ProjectId: grant.ProjectID,
|
||||||
|
RoleKeys: grant.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userGrantCreateToModel(u *UserGrantCreate) *grant_model.UserGrant {
|
||||||
|
return &grant_model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: u.UserId},
|
||||||
|
UserID: u.UserId,
|
||||||
|
ProjectID: u.ProjectId,
|
||||||
|
RoleKeys: u.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userGrantUpdateToModel(u *UserGrantUpdate) *grant_model.UserGrant {
|
||||||
|
return &grant_model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: u.Id},
|
||||||
|
RoleKeys: u.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectUserGrantUpdateToModel(u *ProjectUserGrantUpdate) *grant_model.UserGrant {
|
||||||
|
return &grant_model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: u.Id},
|
||||||
|
RoleKeys: u.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectGrantUserGrantCreateToModel(u *ProjectGrantUserGrantCreate) *grant_model.UserGrant {
|
||||||
|
return &grant_model.UserGrant{
|
||||||
|
UserID: u.UserId,
|
||||||
|
ProjectID: u.ProjectId,
|
||||||
|
RoleKeys: u.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func projectGrantUserGrantUpdateToModel(u *ProjectGrantUserGrantUpdate) *grant_model.UserGrant {
|
||||||
|
return &grant_model.UserGrant{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: u.Id},
|
||||||
|
RoleKeys: u.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func usergrantStateFromModel(state grant_model.UserGrantState) UserGrantState {
|
||||||
|
switch state {
|
||||||
|
case grant_model.USERGRANTSTATE_ACTIVE:
|
||||||
|
return UserGrantState_USERGRANTSTATE_ACTIVE
|
||||||
|
case grant_model.USERGRANTSTATE_INACTIVE:
|
||||||
|
return UserGrantState_USERGRANTSTATE_INACTIVE
|
||||||
|
default:
|
||||||
|
return UserGrantState_USERGRANTSTATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
@ -994,7 +994,6 @@ service ManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//USER_GRANT
|
//USER_GRANT
|
||||||
rpc SearchUserGrants(UserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
rpc SearchUserGrants(UserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@ -1061,6 +1060,16 @@ service ManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc RemoveUserGrant(UserGrantID) returns (google.protobuf.Empty) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/users/{user_id}/grants/{id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (caos.zitadel.utils.v1.auth_option) = {
|
||||||
|
permission: "user.grant.delete"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
//PROJECT_USER_GRANT
|
//PROJECT_USER_GRANT
|
||||||
rpc SearchProjectUserGrants(ProjectUserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
rpc SearchProjectUserGrants(ProjectUserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@ -1362,15 +1371,16 @@ enum SearchMethod {
|
|||||||
|
|
||||||
message UserProfile {
|
message UserProfile {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
UserState state = 2;
|
string first_name = 2;
|
||||||
string first_name = 3;
|
string last_name = 3;
|
||||||
string last_name = 4;
|
string nick_name = 4;
|
||||||
string nick_name = 5;
|
string display_name = 5;
|
||||||
string display_name = 6;
|
string preferred_language = 6;
|
||||||
string preferred_language = 7;
|
Gender gender = 7;
|
||||||
Gender gender = 8;
|
string user_name = 8;
|
||||||
string user_name = 9;
|
uint64 sequence = 9;
|
||||||
uint64 sequence = 10;
|
google.protobuf.Timestamp creation_date = 10;
|
||||||
|
google.protobuf.Timestamp change_date = 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateUserProfileRequest {
|
message UpdateUserProfileRequest {
|
||||||
@ -1388,6 +1398,8 @@ message UserEmail {
|
|||||||
string email = 2;
|
string email = 2;
|
||||||
bool is_email_verified = 3;
|
bool is_email_verified = 3;
|
||||||
uint64 sequence = 4;
|
uint64 sequence = 4;
|
||||||
|
google.protobuf.Timestamp creation_date = 5;
|
||||||
|
google.protobuf.Timestamp change_date = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateUserEmailRequest {
|
message UpdateUserEmailRequest {
|
||||||
@ -1401,6 +1413,8 @@ message UserPhone {
|
|||||||
string phone = 2;
|
string phone = 2;
|
||||||
bool is_phone_verified = 3;
|
bool is_phone_verified = 3;
|
||||||
uint64 sequence = 5;
|
uint64 sequence = 5;
|
||||||
|
google.protobuf.Timestamp creation_date = 6;
|
||||||
|
google.protobuf.Timestamp change_date = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateUserPhoneRequest {
|
message UpdateUserPhoneRequest {
|
||||||
@ -1417,6 +1431,8 @@ message UserAddress {
|
|||||||
string region = 5;
|
string region = 5;
|
||||||
string street_address = 6;
|
string street_address = 6;
|
||||||
uint64 sequence = 7;
|
uint64 sequence = 7;
|
||||||
|
google.protobuf.Timestamp creation_date = 8;
|
||||||
|
google.protobuf.Timestamp change_date = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message UpdateUserAddressRequest {
|
message UpdateUserAddressRequest {
|
||||||
@ -2089,9 +2105,8 @@ message UserGrant {
|
|||||||
|
|
||||||
message UserGrantCreate {
|
message UserGrantCreate {
|
||||||
string user_id = 1;
|
string user_id = 1;
|
||||||
string org_id = 2;
|
string project_id = 2;
|
||||||
string project_id = 3;
|
repeated string role_keys = 3;
|
||||||
repeated string role_keys = 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserGrantUpdate {
|
message UserGrantUpdate {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user