mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 16:07:24 +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() {
|
||||
var configPaths config.ArrayFlags
|
||||
flag.Var(&configPaths, "config-files", "path to the config files")
|
||||
configPaths := config.NewArrayFlags("authz.yaml", "startup.yaml", "system-defaults.yaml")
|
||||
flag.Var(configPaths, "config-files", "paths to the config files")
|
||||
managementEnabled := flag.Bool("management", true, "enable management api")
|
||||
authEnabled := flag.Bool("auth", true, "enable auth api")
|
||||
loginEnabled := flag.Bool("login", true, "enable login ui")
|
||||
@ -41,7 +41,7 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
conf := new(Config)
|
||||
err := config.Read(conf, configPaths...)
|
||||
err := config.Read(conf, configPaths.Values()...)
|
||||
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -1,12 +1,12 @@
|
||||
Tracing:
|
||||
Type: google
|
||||
Config:
|
||||
ProjectID: $TRACING_PROJECT_ID
|
||||
ProjectID: $ZITADEL_TRACING_PROJECT_ID
|
||||
MetricPrefix: ZITADEL-V1
|
||||
Fraction: 1
|
||||
Fraction: $ZITADEL_TRACING_FRACTION
|
||||
|
||||
Log:
|
||||
Level: debug
|
||||
Level: $ZITADEL_LOG_LEVEL
|
||||
Formatter:
|
||||
Format: text
|
||||
|
||||
@ -22,8 +22,8 @@ Mgmt:
|
||||
ServiceName: 'ManagementAPI'
|
||||
Repository:
|
||||
SQL:
|
||||
Host: $CR_HOST
|
||||
Port: $CR_PORT
|
||||
Host: $ZITADEL_EVENTSTORE_HOST
|
||||
Port: $ZITADEL_EVENTSTORE_PORT
|
||||
User: 'management'
|
||||
Database: 'management'
|
||||
SSLmode: disable
|
||||
@ -32,7 +32,6 @@ Mgmt:
|
||||
Config:
|
||||
MaxCacheSizeInByte: 10485760 #10mb
|
||||
|
||||
|
||||
Auth:
|
||||
API:
|
||||
GRPC:
|
||||
|
@ -1,8 +1,44 @@
|
||||
SecretGenerators:
|
||||
PasswordSaltCost: 14
|
||||
ClientSecretGenerator:
|
||||
Length: 64
|
||||
IncludeLowerLetters: true
|
||||
IncludeUpperLetters: true
|
||||
IncludeDigits: true
|
||||
IncludeSymbols: true
|
||||
SystemDefaults:
|
||||
UserVerificationKey:
|
||||
EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY
|
||||
SecretGenerators:
|
||||
PasswordSaltCost: 14
|
||||
ClientSecretGenerator:
|
||||
Length: 64
|
||||
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
|
||||
`show databases;`
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -34,6 +34,7 @@ require (
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||
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/sirupsen/logrus v1.5.0 // indirect
|
||||
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.16 h1:1eabOZiVGl+XB02/VG9NpR+3fngaNc74pgb5RmyzgLY=
|
||||
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/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/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=
|
||||
|
@ -9,13 +9,29 @@ var _ flag.Value = (*ArrayFlags)(nil)
|
||||
|
||||
//ArrayFlags implements the flag/Value interface
|
||||
//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 {
|
||||
return strings.Join(*i, ";")
|
||||
return strings.Join(i.Values(), ";")
|
||||
}
|
||||
|
||||
func (i *ArrayFlags) Set(value string) error {
|
||||
*i = append(*i, value)
|
||||
i.values = append(i.values, value)
|
||||
return nil
|
||||
}
|
||||
|
@ -3,10 +3,25 @@ package systemdefaults
|
||||
import "github.com/caos/zitadel/internal/crypto"
|
||||
|
||||
type SystemDefaults struct {
|
||||
SecretGenerator SecretGenerator
|
||||
SecretGenerators SecretGenerators
|
||||
UserVerificationKey *crypto.KeyConfig
|
||||
Multifactors MultifactorConfig
|
||||
}
|
||||
|
||||
type SecretGenerator struct {
|
||||
PasswordSaltCost int
|
||||
ClientSecretGenerator crypto.GeneratorConfig
|
||||
type SecretGenerators struct {
|
||||
PasswordSaltCost int
|
||||
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"
|
||||
|
||||
"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) {
|
||||
type args struct {
|
||||
creationDate time.Time
|
||||
@ -152,7 +98,7 @@ func TestVerifyCode(t *testing.T) {
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
g: createMockGenerator(t, createMockEncryptionAlg(t)),
|
||||
g: createMockGenerator(t, CreateMockEncryptionAlg(gomock.NewController(t))),
|
||||
},
|
||||
false,
|
||||
},
|
||||
@ -197,7 +143,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
args{
|
||||
cryptoCode: nil,
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
@ -209,7 +155,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
@ -222,7 +168,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
@ -235,7 +181,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
Crypted: nil,
|
||||
},
|
||||
verificationCode: "wrong",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
@ -249,7 +195,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "wrong",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
true,
|
||||
},
|
||||
@ -263,7 +209,7 @@ func Test_verifyEncryptedCode(t *testing.T) {
|
||||
Crypted: []byte("code"),
|
||||
},
|
||||
verificationCode: "code",
|
||||
alg: createMockEncryptionAlg(t),
|
||||
alg: CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
@ -23,3 +23,6 @@ func (o *ObjectRoot) AppendEvent(event *Event) {
|
||||
|
||||
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_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 {
|
||||
@ -17,6 +19,8 @@ type Config struct {
|
||||
type EsRepository struct {
|
||||
//spooler *es_spooler.Spooler
|
||||
ProjectRepo
|
||||
UserRepo
|
||||
UserGrantRepo
|
||||
}
|
||||
|
||||
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 {
|
||||
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{
|
||||
ProjectRepo{project},
|
||||
UserRepo{user},
|
||||
UserGrantRepo{usergrant},
|
||||
}, 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 {
|
||||
Health() error
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerator.PasswordSaltCost)
|
||||
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerator.ClientSecretGenerator, passwordAlg)
|
||||
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
|
||||
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
|
||||
idGenerator := sonyflake.NewSonyflake(sonyflake.Settings{})
|
||||
return &ProjectEventstore{
|
||||
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: "",
|
||||
},
|
||||
|
||||
"/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{
|
||||
Permission: "project.user.grant.read",
|
||||
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) {
|
||||
var protoReq ProjectUserGrantSearchRequest
|
||||
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) {
|
||||
ctx, cancel := context.WithCancel(req.Context())
|
||||
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_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_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_RemoveUserGrant_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ManagementService_SearchProjectUserGrants_0 = runtime.ForwardResponseMessage
|
||||
|
||||
forward_ManagementService_ProjectUserGrantByID_0 = runtime.ForwardResponseMessage
|
||||
|
@ -3185,6 +3185,34 @@
|
||||
"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": {
|
||||
"operationId": "UpdateUserGrant",
|
||||
"responses": {
|
||||
@ -5477,6 +5505,14 @@
|
||||
"sequence": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"creation_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"change_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5496,6 +5532,14 @@
|
||||
"sequence": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"creation_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"change_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5564,9 +5608,6 @@
|
||||
"user_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"org_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"project_id": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -5705,6 +5746,14 @@
|
||||
"sequence": {
|
||||
"type": "string",
|
||||
"format": "uint64"
|
||||
},
|
||||
"creation_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"change_date": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -5714,9 +5763,6 @@
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"$ref": "#/definitions/v1UserState"
|
||||
},
|
||||
"first_name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -5741,6 +5787,14 @@
|
||||
"sequence": {
|
||||
"type": "string",
|
||||
"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...)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (m *MockManagementServiceClient) ResendEmailVerificationMail(arg0 context.Context, arg1 *grpc.UserID, arg2 ...grpc0.CallOption) (*emptypb.Empty, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -13,18 +13,22 @@ import (
|
||||
var _ ManagementServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
port string
|
||||
project repository.ProjectRepository
|
||||
verifier *mgmt_auth.TokenVerifier
|
||||
authZ auth.Config
|
||||
port string
|
||||
project repository.ProjectRepository
|
||||
user repository.UserRepository
|
||||
usergrant repository.UserGrantRepository
|
||||
verifier *mgmt_auth.TokenVerifier
|
||||
authZ auth.Config
|
||||
}
|
||||
|
||||
func StartServer(conf grpc_util.ServerConfig, authZ auth.Config, repo repository.Repository) *Server {
|
||||
return &Server{
|
||||
port: conf.Port,
|
||||
project: repo,
|
||||
authZ: authZ,
|
||||
verifier: mgmt_auth.Start(),
|
||||
port: conf.Port,
|
||||
project: repo,
|
||||
user: repo,
|
||||
usergrant: repo,
|
||||
authZ: authZ,
|
||||
verifier: mgmt_auth.Start(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,12 @@ import (
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
)
|
||||
|
||||
func (s *Server) GetUserByID(ctx context.Context, userID *UserID) (*User, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-0oVbs", "Not implemented")
|
||||
func (s *Server) GetUserByID(ctx context.Context, id *UserID) (*User, error) {
|
||||
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) {
|
||||
@ -26,76 +30,132 @@ func (s *Server) IsUserUnique(ctx context.Context, request *UniqueUserRequest) (
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-olF56", "Not implemented")
|
||||
}
|
||||
|
||||
func (s *Server) CreateUser(ctx context.Context, request *CreateUserRequest) (*User, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-sd4fs", "Not implemented")
|
||||
func (s *Server) CreateUser(ctx context.Context, in *CreateUserRequest) (*User, error) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-Vgh64", "Not implemented")
|
||||
func (s *Server) DeactivateUser(ctx context.Context, in *UserID) (*User, error) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-mCx4f", "Not implemented")
|
||||
func (s *Server) ReactivateUser(ctx context.Context, in *UserID) (*User, error) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-ds4fd", "Not implemented")
|
||||
func (s *Server) LockUser(ctx context.Context, in *UserID) (*User, error) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-MV7dd", "Not implemented")
|
||||
func (s *Server) UnlockUser(ctx context.Context, in *UserID) (*User, error) {
|
||||
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")
|
||||
}
|
||||
|
||||
func (s *Server) GetUserProfile(ctx context.Context, ID *UserID) (*UserProfile, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-mT67d", "Not implemented")
|
||||
func (s *Server) GetUserProfile(ctx context.Context, in *UserID) (*UserProfile, error) {
|
||||
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) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-peo9d", "Not implemented")
|
||||
func (s *Server) GetUserEmail(ctx context.Context, in *UserID) (*UserEmail, error) {
|
||||
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) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-dwsP9", "Not implemented")
|
||||
func (s *Server) ResendEmailVerificationMail(ctx context.Context, in *UserID) (*empty.Empty, error) {
|
||||
err := s.user.CreateEmailVerificationCode(ctx, in.Id)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) GetUserPhone(ctx context.Context, ID *UserID) (*UserPhone, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-wlf7f", "Not implemented")
|
||||
func (s *Server) GetUserPhone(ctx context.Context, in *UserID) (*UserPhone, error) {
|
||||
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) {
|
||||
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) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-98hdE", "Not implemented")
|
||||
func (s *Server) ResendPhoneVerificationCode(ctx context.Context, in *UserID) (*empty.Empty, error) {
|
||||
err := s.user.CreatePhoneVerificationCode(ctx, in.Id)
|
||||
return &empty.Empty{}, err
|
||||
}
|
||||
|
||||
func (s *Server) GetUserAddress(ctx context.Context, ID *UserID) (*UserAddress, error) {
|
||||
return nil, errors.ThrowUnimplemented(nil, "GRPC-plt67", "Not implemented")
|
||||
func (s *Server) GetUserAddress(ctx context.Context, in *UserID) (*UserAddress, error) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
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 (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
)
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
@ -31,22 +57,42 @@ func (s *Server) SearchProjectUserGrants(ctx context.Context, request *ProjectUs
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
@ -54,20 +100,40 @@ func (s *Server) SearchProjectGrantUserGrants(ctx context.Context, request *Proj
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
rpc SearchUserGrants(UserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
||||
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
|
||||
rpc SearchProjectUserGrants(ProjectUserGrantSearchRequest) returns (UserGrantSearchResponse) {
|
||||
option (google.api.http) = {
|
||||
@ -1362,15 +1371,16 @@ enum SearchMethod {
|
||||
|
||||
message UserProfile {
|
||||
string id = 1;
|
||||
UserState state = 2;
|
||||
string first_name = 3;
|
||||
string last_name = 4;
|
||||
string nick_name = 5;
|
||||
string display_name = 6;
|
||||
string preferred_language = 7;
|
||||
Gender gender = 8;
|
||||
string user_name = 9;
|
||||
uint64 sequence = 10;
|
||||
string first_name = 2;
|
||||
string last_name = 3;
|
||||
string nick_name = 4;
|
||||
string display_name = 5;
|
||||
string preferred_language = 6;
|
||||
Gender gender = 7;
|
||||
string user_name = 8;
|
||||
uint64 sequence = 9;
|
||||
google.protobuf.Timestamp creation_date = 10;
|
||||
google.protobuf.Timestamp change_date = 11;
|
||||
}
|
||||
|
||||
message UpdateUserProfileRequest {
|
||||
@ -1388,6 +1398,8 @@ message UserEmail {
|
||||
string email = 2;
|
||||
bool is_email_verified = 3;
|
||||
uint64 sequence = 4;
|
||||
google.protobuf.Timestamp creation_date = 5;
|
||||
google.protobuf.Timestamp change_date = 6;
|
||||
}
|
||||
|
||||
message UpdateUserEmailRequest {
|
||||
@ -1401,6 +1413,8 @@ message UserPhone {
|
||||
string phone = 2;
|
||||
bool is_phone_verified = 3;
|
||||
uint64 sequence = 5;
|
||||
google.protobuf.Timestamp creation_date = 6;
|
||||
google.protobuf.Timestamp change_date = 7;
|
||||
}
|
||||
|
||||
message UpdateUserPhoneRequest {
|
||||
@ -1417,6 +1431,8 @@ message UserAddress {
|
||||
string region = 5;
|
||||
string street_address = 6;
|
||||
uint64 sequence = 7;
|
||||
google.protobuf.Timestamp creation_date = 8;
|
||||
google.protobuf.Timestamp change_date = 9;
|
||||
}
|
||||
|
||||
message UpdateUserAddressRequest {
|
||||
@ -2089,9 +2105,8 @@ message UserGrant {
|
||||
|
||||
message UserGrantCreate {
|
||||
string user_id = 1;
|
||||
string org_id = 2;
|
||||
string project_id = 3;
|
||||
repeated string role_keys = 4;
|
||||
string project_id = 2;
|
||||
repeated string role_keys = 3;
|
||||
}
|
||||
|
||||
message UserGrantUpdate {
|
||||
|
Loading…
x
Reference in New Issue
Block a user