mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:27:23 +00:00
feat: Login, OP Support and Auth Queries (#177)
* fix: change oidc config * fix: change oidc config secret * begin models * begin repo * 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 * begin repo * repo models and more * feat: user command side * lots of functions * user command side * profile requests * commit before rebase on user * save * local config with gopass and more * begin new auth command (user centric) * 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 * move to auth request * 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 * email verification step * more steps * lot of mfa * begin tests * more next steps * auth api * auth api (user) * auth api (user) * auth api (user) * differ requests * merge * tests * fix compilation error * mock for id generator * 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 * begin separation of command and query * otp * change packages * some cleanup and fixes * tests for auth request / next steps * add VerificationLifetimes to config and make it run * tests * fix code challenge validation * cleanup * fix merge * begin view * repackaging tests and configs * fix startup config for auth * add migration * add PromptSelectAccount * fix copy / paste * remove user_agent files * fixes * fix sequences in user_session * token commands * token queries and signout * fix * fix set password test * add token handler and table * handle session init * add session state * add user view test cases * change VerifyMyMfaOTP * some fixes * fix user repo in auth api * cleanup * add user session view test * fix merge * begin oidc * user agent and more * config * keys * key command and query * add login statics * key handler * start login * login handlers * lot of fixes * merge oidc * add missing exports * add missing exports * fix some bugs * authrequestid in htmls * getrequest * update auth request * fix userid check * add username to authrequest * fix user session and auth request handling * fix UserSessionsByAgentID * fix auth request tests * fix user session on UserPasswordChanged and MfaOtpRemoved * fix MfaTypesSetupPossible * handle mfa * fill username * auth request query checks new events * fix userSessionByIDs * fix tokens * fix userSessionByIDs test * add user selection * init code * user code creation date * add init user step * add verification failed types * add verification failures * verify init code * user init code handle * user init code handle * fix userSessionByIDs * update logging * user agent cookie * browserinfo from request * add DeleteAuthRequest * add static login files to binary * add login statik to build * move generate to separate file and remove statik.go files * remove static dirs from startup.yaml * generate into separate namespaces * merge master * auth request code * auth request type mapping * fix keys * improve tokens * improve register and basic styling * fix ailerons font * improve password reset * add audience to token * all oidc apps as audience * fix test nextStep * fix email texts * remove "not set" * lot of style changes * improve copy to clipboard * fix footer * add cookie handler * remove placeholders * fix compilation after merge * fix auth config * remove comments * typo * use new secrets store * change default pws to match default policy * fixes * add todo * enable login * fix db name * Auth queries (#179) * my usersession * org structure/ auth handlers * working user grant spooler * auth internal user grants * search my project orgs * remove permissions file * my zitadel permissions * my zitadel permissions * remove unused code * authz * app searches in view * token verification * fix user grant load * fix tests * fix tests * read configs * remove unused const * remove todos * env variables * app_name * working authz * search projects * global resourceowner * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * model2 rename * at least it works * check token expiry * search my user grants * remove token table from authz Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix test * fix ports and enable console Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
parent
46b60a6968
commit
8a5badddf6
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -101,6 +101,8 @@ jobs:
|
|||||||
- run: go get github.com/rakyll/statik
|
- run: go get github.com/rakyll/statik
|
||||||
- run: ./build/console/generate-static.sh
|
- run: ./build/console/generate-static.sh
|
||||||
- run: cat pkg/console/statik/statik.go
|
- run: cat pkg/console/statik/statik.go
|
||||||
|
- run: ./build/login/generate-static.sh
|
||||||
|
- run: cat internal/login/statik/statik.go
|
||||||
- run: CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o zitadel-${{ matrix.goos }}-${{ matrix.goarch }} cmd/zitadel/main.go
|
- run: CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o zitadel-${{ matrix.goos }}-${{ matrix.goarch }} cmd/zitadel/main.go
|
||||||
- uses: actions/upload-artifact@v1
|
- uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -32,4 +32,4 @@ cockroach-data/*
|
|||||||
|
|
||||||
#binaries
|
#binaries
|
||||||
cmd/zitadel/zitadel
|
cmd/zitadel/zitadel
|
||||||
zitadel
|
**/statik/statik.go
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
set -eux
|
set -eux
|
||||||
|
|
||||||
go generate pkg/console/console.go
|
go generate pkg/console/statik/generate.go
|
5
build/login/generate-static.sh
Executable file
5
build/login/generate-static.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
go generate internal/login/statik/generate.go
|
@ -1,4 +1,4 @@
|
|||||||
AuthZ:
|
InternalAuthZ:
|
||||||
RolePermissionMappings:
|
RolePermissionMappings:
|
||||||
- Role: 'IAM_OWNER'
|
- Role: 'IAM_OWNER'
|
||||||
Permissions:
|
Permissions:
|
||||||
|
26
cmd/zitadel/caos_local.sh
Normal file → Executable file
26
cmd/zitadel/caos_local.sh
Normal file → Executable file
@ -1,7 +1,9 @@
|
|||||||
BASEDIR=$(dirname "$0")
|
BASEDIR=$(dirname "$0")
|
||||||
|
|
||||||
|
gopass sync --store zitadel-secrets
|
||||||
|
|
||||||
# Tracing
|
# Tracing
|
||||||
gopass citadel-secrets/citadel/developer/default/citadel-svc-account-eventstore-local | base64 -D > "$BASEDIR/local_svc-account-tracing.json"
|
gopass zitadel-secrets/zitadel/developer/default/zitadel-svc-account-eventstore-local | base64 -D > "$BASEDIR/local_svc-account-tracing.json"
|
||||||
export GOOGLE_APPLICATION_CREDENTIALS="$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_PROJECT_ID=caos-citadel-test
|
||||||
@ -15,24 +17,32 @@ export ZITADEL_EVENTSTORE_HOST=localhost
|
|||||||
export ZITADEL_EVENTSTORE_PORT=26257
|
export ZITADEL_EVENTSTORE_PORT=26257
|
||||||
|
|
||||||
# Keys
|
# Keys
|
||||||
gopass citadel-secrets/citadel/developer/default/keys.yaml > "$BASEDIR/local_keys.yaml"
|
gopass zitadel-secrets/zitadel/developer/default/keys.yaml > "$BASEDIR/local_keys.yaml"
|
||||||
export ZITADEL_KEY_PATH="$BASEDIR/local_keys.yaml"
|
export ZITADEL_KEY_PATH="$BASEDIR/local_keys.yaml"
|
||||||
|
|
||||||
export ZITADEL_USER_VERIFICATION_KEY=UserVerificationKey_1
|
export ZITADEL_USER_VERIFICATION_KEY=UserVerificationKey_1
|
||||||
export ZITADEL_OTP_VERIFICATION_KEY=OTPVerificationKey_1
|
export ZITADEL_OTP_VERIFICATION_KEY=OTPVerificationKey_1
|
||||||
|
export ZITADEL_OIDC_KEYS_ID=OIDCKey_1
|
||||||
|
export ZITADEL_COOKIE_KEY=CookieKey_1
|
||||||
|
|
||||||
# Notifications
|
# Notifications
|
||||||
export DEBUG_MODE=TRUE
|
export DEBUG_MODE=TRUE
|
||||||
export TWILIO_SERVICE_SID=$(gopass citadel-secrets/citadel/developer/default/twilio-sid)
|
export TWILIO_SERVICE_SID=$(gopass zitadel-secrets/zitadel/dev/twilio-sid)
|
||||||
export TWILIO_TOKEN=$(gopass citadel-secrets/citadel/developer/default/twilio-auth-token)
|
export TWILIO_TOKEN=$(gopass zitadel-secrets/zitadel/dev/twilio-auth-token)
|
||||||
export TWILIO_SENDER_NAME=CAOS AG
|
export TWILIO_SENDER_NAME=CAOS AG
|
||||||
export SMTP_HOST=smtp.gmail.com:465
|
export SMTP_HOST=smtp.gmail.com:465
|
||||||
export SMTP_USER=zitadel@caos.ch
|
export SMTP_USER=zitadel@caos.ch
|
||||||
export SMTP_PASSWORD=$(gopass citadel-secrets/citadel/google/emailappkey)
|
export SMTP_PASSWORD=$(gopass zitadel-secrets/zitadel/google/emailappkey)
|
||||||
export EMAIL_SENDER_ADDRESS=noreply@caos.ch
|
export EMAIL_SENDER_ADDRESS=noreply@caos.ch
|
||||||
export EMAIL_SENDER_NAME=CAOS AG
|
export EMAIL_SENDER_NAME=CAOS AG
|
||||||
export SMTP_TLS=TRUE
|
export SMTP_TLS=TRUE
|
||||||
export CHAT_URL=$(gopass citadel-secrets/citadel/developer/default/google-chat-url | base64 -D)
|
export CHAT_URL=$(gopass zitadel-secrets/zitadel/dev/google-chat-url | base64 -D)
|
||||||
|
|
||||||
# Zitadel
|
#OIDC
|
||||||
export ZITADEL_ACCOUNTS=http://localhost:61121
|
export ZITADEL_ISSUER=http://localhost:50022
|
||||||
|
export ZITADEL_ACCOUNTS=http://localhost:50031
|
||||||
|
export ZITADEL_AUTHORIZE=http://localhost:50022
|
||||||
|
export ZITADEL_OAUTH=http://localhost:50022
|
||||||
|
export ZITADEL_CONSOLE=http://localhost:4200
|
||||||
|
export CAOS_OIDC_DEV=true
|
||||||
|
export ZITADEL_COOKIE_DOMAIN=localhost
|
@ -3,18 +3,20 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
|
||||||
|
"github.com/caos/zitadel/internal/authz"
|
||||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/login"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
|
||||||
authz "github.com/caos/zitadel/internal/api/auth"
|
internal_authz "github.com/caos/zitadel/internal/api/auth"
|
||||||
"github.com/caos/zitadel/internal/config"
|
"github.com/caos/zitadel/internal/config"
|
||||||
"github.com/caos/zitadel/internal/notification"
|
"github.com/caos/zitadel/internal/notification"
|
||||||
tracing "github.com/caos/zitadel/internal/tracing/config"
|
tracing "github.com/caos/zitadel/internal/tracing/config"
|
||||||
"github.com/caos/zitadel/pkg/admin"
|
"github.com/caos/zitadel/pkg/admin"
|
||||||
"github.com/caos/zitadel/pkg/auth"
|
"github.com/caos/zitadel/pkg/auth"
|
||||||
"github.com/caos/zitadel/pkg/console"
|
"github.com/caos/zitadel/pkg/console"
|
||||||
"github.com/caos/zitadel/pkg/login"
|
|
||||||
"github.com/caos/zitadel/pkg/management"
|
"github.com/caos/zitadel/pkg/management"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,13 +24,14 @@ type Config struct {
|
|||||||
Mgmt management.Config
|
Mgmt management.Config
|
||||||
Auth auth.Config
|
Auth auth.Config
|
||||||
Login login.Config
|
Login login.Config
|
||||||
|
AuthZ authz.Config
|
||||||
Admin admin.Config
|
Admin admin.Config
|
||||||
Console console.Config
|
Console console.Config
|
||||||
Notification notification.Config
|
Notification notification.Config
|
||||||
|
|
||||||
Log logging.Config
|
Log logging.Config
|
||||||
Tracing tracing.TracingConfig
|
Tracing tracing.TracingConfig
|
||||||
AuthZ authz.Config
|
InternalAuthZ internal_authz.Config
|
||||||
SystemDefaults sd.SystemDefaults
|
SystemDefaults sd.SystemDefaults
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,18 +51,25 @@ func main() {
|
|||||||
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
|
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
authZRepo, err := authz.Start(ctx, conf.AuthZ, conf.InternalAuthZ, conf.SystemDefaults)
|
||||||
|
logging.Log("MAIN-s9KOw").OnError(err).Fatal("error starting authz repo")
|
||||||
|
|
||||||
if *adminEnabled {
|
if *adminEnabled {
|
||||||
admin.Start(ctx, conf.Admin, conf.AuthZ, conf.SystemDefaults)
|
admin.Start(ctx, conf.Admin, authZRepo, conf.InternalAuthZ, conf.SystemDefaults)
|
||||||
}
|
}
|
||||||
if *managementEnabled {
|
if *managementEnabled {
|
||||||
management.Start(ctx, conf.Mgmt, conf.AuthZ, conf.SystemDefaults)
|
management.Start(ctx, conf.Mgmt, authZRepo, conf.InternalAuthZ, conf.SystemDefaults)
|
||||||
|
}
|
||||||
|
var authRepo *eventsourcing.EsRepository
|
||||||
|
if *authEnabled || *loginEnabled {
|
||||||
|
authRepo, err = eventsourcing.Start(conf.Auth.Repository, conf.InternalAuthZ, conf.SystemDefaults, authZRepo)
|
||||||
|
logging.Log("MAIN-9oRw6").OnError(err).Fatal("error starting auth repo")
|
||||||
}
|
}
|
||||||
if *authEnabled {
|
if *authEnabled {
|
||||||
auth.Start(ctx, conf.Auth, conf.AuthZ, conf.SystemDefaults)
|
auth.Start(ctx, conf.Auth, authZRepo, conf.InternalAuthZ, conf.SystemDefaults, authRepo)
|
||||||
}
|
}
|
||||||
if *loginEnabled {
|
if *loginEnabled {
|
||||||
err = login.Start(ctx, conf.Login)
|
login.Start(ctx, conf.Login, conf.SystemDefaults, authRepo)
|
||||||
logging.Log("MAIN-53RF2").OnError(err).Fatal("error starting login ui")
|
|
||||||
}
|
}
|
||||||
if *notificationEnabled {
|
if *notificationEnabled {
|
||||||
notification.Start(ctx, conf.Notification, conf.SystemDefaults)
|
notification.Start(ctx, conf.Notification, conf.SystemDefaults)
|
||||||
|
@ -50,6 +50,37 @@ Auth:
|
|||||||
GatewayPort: 50021
|
GatewayPort: 50021
|
||||||
CustomHeaders:
|
CustomHeaders:
|
||||||
- x-zitadel-
|
- x-zitadel-
|
||||||
|
OIDC:
|
||||||
|
OPConfig:
|
||||||
|
Issuer: $ZITADEL_ISSUER
|
||||||
|
DefaultLogoutRedirectURI: $ZITADEL_ACCOUNTS/logout/done
|
||||||
|
Port: 50022
|
||||||
|
StorageConfig:
|
||||||
|
DefaultLoginURL: $ZITADEL_ACCOUNTS/login?authRequestID=
|
||||||
|
DefaultAccessTokenLifetime: 12h
|
||||||
|
DefaultIdTokenLifetime: 12h
|
||||||
|
SigningKeyAlgorithm: RS256
|
||||||
|
UserAgentCookieConfig:
|
||||||
|
Name: caos.zitadel.useragent
|
||||||
|
Domain: $ZITADEL_COOKIE_DOMAIN
|
||||||
|
Key:
|
||||||
|
EncryptionKeyID: $ZITADEL_COOKIE_KEY
|
||||||
|
Endpoints:
|
||||||
|
Auth:
|
||||||
|
Path: 'authorize'
|
||||||
|
URL: '$ZITADEL_AUTHORIZE/authorize'
|
||||||
|
Token:
|
||||||
|
Path: 'token'
|
||||||
|
URL: '$ZITADEL_OAUTH/token'
|
||||||
|
EndSession:
|
||||||
|
Path: 'endsession'
|
||||||
|
URL: '$ZITADEL_AUTHORIZE/endsession'
|
||||||
|
Userinfo:
|
||||||
|
Path: 'userinfo'
|
||||||
|
URL: '$ZITADEL_OAUTH/userinfo'
|
||||||
|
Keys:
|
||||||
|
Path: 'keys'
|
||||||
|
URL: '$ZITADEL_OAUTH/keys'
|
||||||
Repository:
|
Repository:
|
||||||
SearchLimit: 100
|
SearchLimit: 100
|
||||||
Eventstore:
|
Eventstore:
|
||||||
@ -66,11 +97,12 @@ Auth:
|
|||||||
Config:
|
Config:
|
||||||
MaxCacheSizeInByte: 10485760 #10mb
|
MaxCacheSizeInByte: 10485760 #10mb
|
||||||
AuthRequest:
|
AuthRequest:
|
||||||
Host: $ZITADEL_EVENTSTORE_HOST
|
Connection:
|
||||||
Port: $ZITADEL_EVENTSTORE_PORT
|
Host: $ZITADEL_EVENTSTORE_HOST
|
||||||
User: 'auth'
|
Port: $ZITADEL_EVENTSTORE_PORT
|
||||||
Database: 'auth'
|
User: 'auth'
|
||||||
SSLmode: disable
|
Database: 'auth'
|
||||||
|
SSLmode: disable
|
||||||
View:
|
View:
|
||||||
Host: $ZITADEL_EVENTSTORE_HOST
|
Host: $ZITADEL_EVENTSTORE_HOST
|
||||||
Port: $ZITADEL_EVENTSTORE_PORT
|
Port: $ZITADEL_EVENTSTORE_PORT
|
||||||
@ -81,9 +113,48 @@ Auth:
|
|||||||
ConcurrentTasks: 4
|
ConcurrentTasks: 4
|
||||||
BulkLimit: 100
|
BulkLimit: 100
|
||||||
FailureCountUntilSkip: 5
|
FailureCountUntilSkip: 5
|
||||||
|
KeyConfig:
|
||||||
|
Size: 2048
|
||||||
|
PrivateKeyLifetime: 6h
|
||||||
|
PublicKeyLifetime: 30h
|
||||||
|
EncryptionConfig:
|
||||||
|
EncryptionKeyID: $ZITADEL_OIDC_KEYS_ID
|
||||||
|
SigningKeyRotation: 10s
|
||||||
|
|
||||||
Login:
|
Login:
|
||||||
# will get port range 5003x
|
Handler:
|
||||||
|
Port: 50031
|
||||||
|
OidcAuthCallbackURL: '$ZITADEL_AUTHORIZE/authorize/'
|
||||||
|
ZitadelURL: '$ZITADEL_CONSOLE'
|
||||||
|
LanguageCookieName: 'caos.zitadel.login.lang'
|
||||||
|
DefaultLanguage: 'de'
|
||||||
|
|
||||||
|
|
||||||
|
AuthZ:
|
||||||
|
Repository:
|
||||||
|
Eventstore:
|
||||||
|
ServiceName: 'AuthZ'
|
||||||
|
Repository:
|
||||||
|
SQL:
|
||||||
|
Host: $ZITADEL_EVENTSTORE_HOST
|
||||||
|
Port: $ZITADEL_EVENTSTORE_PORT
|
||||||
|
User: 'authz'
|
||||||
|
Database: 'eventstore'
|
||||||
|
SSLmode: disable
|
||||||
|
Cache:
|
||||||
|
Type: 'fastcache'
|
||||||
|
Config:
|
||||||
|
MaxCacheSizeInByte: 10485760 #10mb
|
||||||
|
View:
|
||||||
|
Host: $ZITADEL_EVENTSTORE_HOST
|
||||||
|
Port: $ZITADEL_EVENTSTORE_PORT
|
||||||
|
User: 'authz'
|
||||||
|
Database: 'authz'
|
||||||
|
SSLmode: disable
|
||||||
|
Spooler:
|
||||||
|
ConcurrentTasks: 1
|
||||||
|
BulkLimit: 100
|
||||||
|
FailureCountUntilSkip: 5
|
||||||
|
|
||||||
Admin:
|
Admin:
|
||||||
API:
|
API:
|
||||||
@ -120,7 +191,6 @@ Admin:
|
|||||||
|
|
||||||
Console:
|
Console:
|
||||||
Port: 50050
|
Port: 50050
|
||||||
StaticDir: /app/console/dist
|
|
||||||
|
|
||||||
|
|
||||||
Notification:
|
Notification:
|
||||||
|
@ -76,7 +76,7 @@ SystemDefaults:
|
|||||||
LastName: 'Administrator'
|
LastName: 'Administrator'
|
||||||
UserName: 'zitadel-global-org-admin@caos.ch'
|
UserName: 'zitadel-global-org-admin@caos.ch'
|
||||||
Email: 'zitadel-global-org-admin@caos.ch'
|
Email: 'zitadel-global-org-admin@caos.ch'
|
||||||
Password: 'Password'
|
Password: 'Password1!'
|
||||||
Owners:
|
Owners:
|
||||||
- 'zitadel-global-org-admin@caos.ch'
|
- 'zitadel-global-org-admin@caos.ch'
|
||||||
- Name: 'CAOS AG'
|
- Name: 'CAOS AG'
|
||||||
@ -86,7 +86,7 @@ SystemDefaults:
|
|||||||
LastName: 'Administrator'
|
LastName: 'Administrator'
|
||||||
UserName: 'zitadel-admin@caos.ch'
|
UserName: 'zitadel-admin@caos.ch'
|
||||||
Email: 'zitadel-admin@caos.ch'
|
Email: 'zitadel-admin@caos.ch'
|
||||||
Password: 'Password'
|
Password: 'Password1!'
|
||||||
Owners:
|
Owners:
|
||||||
- 'zitadel-admin@caos.ch'
|
- 'zitadel-admin@caos.ch'
|
||||||
Projects:
|
Projects:
|
||||||
@ -97,9 +97,9 @@ SystemDefaults:
|
|||||||
- Name: 'Admin-API'
|
- Name: 'Admin-API'
|
||||||
- Name: 'Zitadel Console'
|
- Name: 'Zitadel Console'
|
||||||
RedirectUris:
|
RedirectUris:
|
||||||
- '$CITADEL_CONSOLE/auth/callback'
|
- '$ZITADEL_CONSOLE/auth/callback'
|
||||||
PostLogoutRedirectUris:
|
PostLogoutRedirectUris:
|
||||||
- '$CITADEL_CONSOLE/signedout'
|
- '$ZITADEL_CONSOLE/signedout'
|
||||||
ResponseTypes:
|
ResponseTypes:
|
||||||
- 'CODE'
|
- 'CODE'
|
||||||
GrantTypes:
|
GrantTypes:
|
||||||
@ -133,9 +133,9 @@ SystemDefaults:
|
|||||||
From: $TWILIO_SENDER_NAME
|
From: $TWILIO_SENDER_NAME
|
||||||
TemplateData:
|
TemplateData:
|
||||||
InitCode:
|
InitCode:
|
||||||
Title: 'Zitadel - User Initialisieren'
|
Title: 'Zitadel - User initialisieren'
|
||||||
PreHeader: 'User Initialisieren'
|
PreHeader: 'User initialisieren'
|
||||||
Subject: 'User Initialisieren'
|
Subject: 'User initialisieren'
|
||||||
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
||||||
Text: 'Dieser Benutzer wurde soeben im Zitadel erstellt. Du kannst den Button unten verwenden, um die Initialisierung abzuschliesen. Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.'
|
Text: 'Dieser Benutzer wurde soeben im Zitadel erstellt. Du kannst den Button unten verwenden, um die Initialisierung abzuschliesen. Falls du dieses Mail nicht angefordert hast, kannst du es einfach ignorieren.'
|
||||||
ButtonText: 'Initialisierung abschliessen'
|
ButtonText: 'Initialisierung abschliessen'
|
||||||
@ -147,16 +147,16 @@ SystemDefaults:
|
|||||||
Text: 'Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen. Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.'
|
Text: 'Wir haben eine Anfrage für das Zurücksetzen deines Passwortes bekommen. Du kannst den untenstehenden Button verwenden, um dein Passwort zurückzusetzen. Falls du dieses Mail nicht angefordert hast, kannst du es ignorieren.'
|
||||||
ButtonText: 'Passwort zurücksetzen'
|
ButtonText: 'Passwort zurücksetzen'
|
||||||
VerifyEmail:
|
VerifyEmail:
|
||||||
Title: 'Zitadel - Email Verifizieren'
|
Title: 'Zitadel - Email verifizieren'
|
||||||
PreHeader: 'Email verifizieren'
|
PreHeader: 'Email verifizieren'
|
||||||
Subject: 'Email verifizieren'
|
Subject: 'Email verifizieren'
|
||||||
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
||||||
Text: 'Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren. Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.'
|
Text: 'Eine neue E-Mail Adresse wurde hinzugefügt. Bitte verwende den untenstehenden Button um diese zu verifizieren. Falls du deine E-Mail Adresse nicht selber hinzugefügt hast, kannst du dieses E-Mail ignorieren.'
|
||||||
ButtonText: 'Passwort zurücksetzen'
|
ButtonText: 'Email verifizieren'
|
||||||
VerifyPhone:
|
VerifyPhone:
|
||||||
Title: 'Zitadel - Telefonnummer Verifizieren'
|
Title: 'Zitadel - Telefonnummer verifizieren'
|
||||||
PreHeader: 'Telefonnummer Verifizieren'
|
PreHeader: 'Telefonnummer verifizieren'
|
||||||
Subject: 'Telefonnummer Verifizieren'
|
Subject: 'Telefonnummer verifizieren'
|
||||||
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
|
||||||
Text: 'Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst: {{.Code}}'
|
Text: 'Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst: {{.Code}}'
|
||||||
ButtonText: 'Telefon verifizieren'
|
ButtonText: 'Telefon verifizieren'
|
12
go.mod
12
go.mod
@ -11,16 +11,20 @@ require (
|
|||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||||
github.com/VictoriaMetrics/fastcache v1.5.7
|
github.com/VictoriaMetrics/fastcache v1.5.7
|
||||||
|
github.com/aaronarduino/goqrsvg v0.0.0-20170617203649-603647895681
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca
|
||||||
github.com/allegro/bigcache v1.2.1
|
github.com/allegro/bigcache v1.2.1
|
||||||
github.com/aws/aws-sdk-go v1.30.25 // indirect
|
github.com/aws/aws-sdk-go v1.30.25 // indirect
|
||||||
github.com/caos/logging v0.0.1
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
|
||||||
|
github.com/caos/logging v0.0.2
|
||||||
|
github.com/caos/oidc v0.6.2
|
||||||
github.com/cockroachdb/cockroach-go v0.0.0-20200504194139-73ffeee90b62
|
github.com/cockroachdb/cockroach-go v0.0.0-20200504194139-73ffeee90b62
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0
|
github.com/envoyproxy/protoc-gen-validate v0.1.0
|
||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||||
github.com/golang/mock v1.4.3
|
github.com/golang/mock v1.4.3
|
||||||
github.com/golang/protobuf v1.4.1
|
github.com/golang/protobuf v1.4.1
|
||||||
github.com/google/uuid v1.1.1 // indirect
|
github.com/gorilla/mux v1.7.4
|
||||||
github.com/gorilla/schema v1.1.0
|
github.com/gorilla/schema v1.1.0
|
||||||
github.com/gorilla/securecookie v1.1.1
|
github.com/gorilla/securecookie v1.1.1
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
||||||
@ -41,7 +45,6 @@ require (
|
|||||||
github.com/pquerna/otp v1.2.0
|
github.com/pquerna/otp v1.2.0
|
||||||
github.com/rakyll/statik v0.1.7
|
github.com/rakyll/statik v0.1.7
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
github.com/sirupsen/logrus v1.6.0 // indirect
|
|
||||||
github.com/sony/sonyflake v1.0.0
|
github.com/sony/sonyflake v1.0.0
|
||||||
github.com/stretchr/testify v1.5.1
|
github.com/stretchr/testify v1.5.1
|
||||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
|
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
|
||||||
@ -49,12 +52,13 @@ require (
|
|||||||
go.opencensus.io v0.22.3
|
go.opencensus.io v0.22.3
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 // indirect
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect
|
||||||
golang.org/x/text v0.3.2
|
golang.org/x/text v0.3.2
|
||||||
golang.org/x/tools v0.0.0-20200512001501-aaeff5de670a
|
golang.org/x/tools v0.0.0-20200512001501-aaeff5de670a
|
||||||
google.golang.org/api v0.24.0 // indirect
|
google.golang.org/api v0.24.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380
|
||||||
google.golang.org/grpc v1.29.1
|
google.golang.org/grpc v1.29.1
|
||||||
google.golang.org/protobuf v1.22.0
|
google.golang.org/protobuf v1.22.0
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1
|
||||||
gopkg.in/yaml.v2 v2.2.8 // indirect
|
gopkg.in/yaml.v2 v2.2.8 // indirect
|
||||||
)
|
)
|
||||||
|
30
go.sum
30
go.sum
@ -45,6 +45,10 @@ github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZC
|
|||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
|
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
|
||||||
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
|
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
|
||||||
|
github.com/aaronarduino/goqrsvg v0.0.0-20170617203649-603647895681 h1:eZrVcUgy0P6+B6Vu7SKPh3UZQS5nEuyjhbkFyfz7I2I=
|
||||||
|
github.com/aaronarduino/goqrsvg v0.0.0-20170617203649-603647895681/go.mod h1:dytw+5qs+pdi61fO/S4OmXR7AuEq/HvNCuG03KxQHT4=
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
|
||||||
|
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||||
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||||
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
|
github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
|
||||||
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM=
|
||||||
@ -56,10 +60,11 @@ github.com/aws/aws-sdk-go v1.30.25 h1:89NXJkfpjnMEnsxkP8MVX+LDsoiLCSqevraLb5y4Mj
|
|||||||
github.com/aws/aws-sdk-go v1.30.25/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
github.com/aws/aws-sdk-go v1.30.25/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 h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
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.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
||||||
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
|
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
|
||||||
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
||||||
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
github.com/caos/oidc v0.6.2 h1:ejp6uTepVSlgLoPhXIAqFe8SbCdoxqO4vZv1H4fF0Yk=
|
||||||
|
github.com/caos/oidc v0.6.2/go.mod h1:ozoi3b+aY33gzdvjz4w90VZShIHGsmDa0goruuV0arQ=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
@ -138,6 +143,10 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-github/v31 v31.0.0/go.mod h1:NQPZol8/1sMoWYGN2yaALIBytu17gAWfhbweiEed3pM=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@ -152,6 +161,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
|
||||||
|
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||||
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
|
||||||
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
@ -273,6 +286,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@ -320,6 +334,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
@ -333,6 +348,7 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191122200657-5d9234df094c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -372,8 +388,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25 h1:OKbAoGs4fGM5cPLlVQLZGYkFC8OnOfgo6tt0Smf9XhM=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -498,6 +514,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||||
|
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -3,23 +3,34 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/caos/zitadel/internal/api/auth"
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
adminName = "Admin-API"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenVerifier struct {
|
type TokenVerifier struct {
|
||||||
|
adminID string
|
||||||
|
authZRepo *authz_repo.EsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start() (v *TokenVerifier) {
|
func Start(authZRepo *authz_repo.EsRepository) (v *TokenVerifier) {
|
||||||
return new(TokenVerifier)
|
return &TokenVerifier{authZRepo: authZRepo}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
||||||
return "", "", "", nil
|
userID, clientID, agentID, err := v.authZRepo.VerifyAccessToken(ctx, token, adminName, v.adminID)
|
||||||
|
if clientID != "" {
|
||||||
|
v.adminID = clientID
|
||||||
|
}
|
||||||
|
return userID, clientID, agentID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) ResolveGrants(ctx context.Context, userID, orgID string) ([]*auth.Grant, error) {
|
func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*auth.Grant, error) {
|
||||||
return nil, nil
|
return v.authZRepo.ResolveGrants(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||||
return "", nil
|
return v.authZRepo.ProjectIDByClientID(ctx, clientID)
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,12 @@ package eventstore
|
|||||||
import (
|
import (
|
||||||
admin_model "github.com/caos/zitadel/internal/admin/model"
|
admin_model "github.com/caos/zitadel/internal/admin/model"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
org_model "github.com/caos/zitadel/internal/org/model"
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
|
||||||
usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Setup struct {
|
type Setup struct {
|
||||||
*org_es.Org
|
*model.Org
|
||||||
*usr_es.User
|
*usr_es.User
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,7 +16,7 @@ func (s *Setup) AppendEvents(events ...*es_models.Event) error {
|
|||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
var err error
|
var err error
|
||||||
switch event.AggregateType {
|
switch event.AggregateType {
|
||||||
case org_model.OrgAggregate:
|
case model.OrgAggregate:
|
||||||
err = s.Org.AppendEvent(event)
|
err = s.Org.AppendEvent(event)
|
||||||
case usr_es.UserAggregate:
|
case usr_es.UserAggregate:
|
||||||
err = s.User.AppendEvent(event)
|
err = s.User.AppendEvent(event)
|
||||||
@ -31,7 +30,7 @@ func (s *Setup) AppendEvents(events ...*es_models.Event) error {
|
|||||||
|
|
||||||
func SetupToModel(setup *Setup) *admin_model.SetupOrg {
|
func SetupToModel(setup *Setup) *admin_model.SetupOrg {
|
||||||
return &admin_model.SetupOrg{
|
return &admin_model.SetupOrg{
|
||||||
Org: org_es.OrgToModel(setup.Org),
|
Org: model.OrgToModel(setup.Org),
|
||||||
User: usr_es.UserToModel(setup.User),
|
User: usr_es.UserToModel(setup.User),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
org_model "github.com/caos/zitadel/internal/org/model"
|
|
||||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
"github.com/caos/zitadel/internal/org/repository/view"
|
"github.com/caos/zitadel/internal/org/repository/view"
|
||||||
)
|
)
|
||||||
@ -37,9 +37,9 @@ func (o *Org) Process(event *es_models.Event) error {
|
|||||||
org := new(view.OrgView)
|
org := new(view.OrgView)
|
||||||
|
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case org_model.OrgAdded:
|
case model.OrgAdded:
|
||||||
org.AppendEvent(event)
|
org.AppendEvent(event)
|
||||||
case org_model.OrgChanged:
|
case model.OrgChanged:
|
||||||
err := org.SetData(event)
|
err := org.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -125,7 +125,7 @@ func (s *Setup) Execute(ctx context.Context) error {
|
|||||||
|
|
||||||
err = setUp.setIamProject(ctx)
|
err = setUp.setIamProject(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.Log("SETUP-kaWjq").WithError(err).Error("unable to set citadel project")
|
logging.Log("SETUP-kaWjq").WithError(err).Error("unable to set zitadel project")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
type key int
|
type key int
|
||||||
|
|
||||||
var (
|
const (
|
||||||
permissionsKey key
|
permissionsKey key = 1
|
||||||
dataKey key
|
dataKey key = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type CtxData struct {
|
type CtxData struct {
|
||||||
@ -36,7 +36,7 @@ type Grant struct {
|
|||||||
|
|
||||||
type TokenVerifier interface {
|
type TokenVerifier interface {
|
||||||
VerifyAccessToken(ctx context.Context, token string) (string, string, string, error)
|
VerifyAccessToken(ctx context.Context, token string) (string, string, string, error)
|
||||||
ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error)
|
ResolveGrant(ctx context.Context) (*Grant, error)
|
||||||
GetProjectIDByClientID(ctx context.Context, clientID string) (string, error)
|
GetProjectIDByClientID(ctx context.Context, clientID string) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,21 +11,20 @@ func getUserMethodPermissions(ctx context.Context, t TokenVerifier, requiredPerm
|
|||||||
if ctxData.IsZero() {
|
if ctxData.IsZero() {
|
||||||
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
|
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
|
||||||
}
|
}
|
||||||
grants, err := t.ResolveGrants(ctx, ctxData.UserID, ctxData.OrgID)
|
grant, err := t.ResolveGrant(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
permissions := mapGrantsToPermissions(requiredPerm, grants, authConfig)
|
permissions := mapGrantToPermissions(requiredPerm, grant, authConfig)
|
||||||
return context.WithValue(ctx, permissionsKey, permissions), permissions, nil
|
return context.WithValue(ctx, permissionsKey, permissions), permissions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapGrantsToPermissions(requiredPerm string, grants []*Grant, authConfig *Config) []string {
|
func mapGrantToPermissions(requiredPerm string, grant *Grant, authConfig *Config) []string {
|
||||||
resolvedPermissions := make([]string, 0)
|
resolvedPermissions := make([]string, 0)
|
||||||
for _, grant := range grants {
|
for _, role := range grant.Roles {
|
||||||
for _, role := range grant.Roles {
|
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
|
||||||
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvedPermissions
|
return resolvedPermissions
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ func mapRoleToPerm(requiredPerm, actualRole string, authConfig *Config, resolved
|
|||||||
for _, p := range perms {
|
for _, p := range perms {
|
||||||
if p == requiredPerm {
|
if p == requiredPerm {
|
||||||
p = addRoleContextIDToPerm(p, roleContextID)
|
p = addRoleContextIDToPerm(p, roleContextID)
|
||||||
if !existsPerm(resolvedPermissions, p) {
|
if !ExistsPerm(resolvedPermissions, p) {
|
||||||
resolvedPermissions = append(resolvedPermissions, p)
|
resolvedPermissions = append(resolvedPermissions, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,7 +50,7 @@ func addRoleContextIDToPerm(perm, roleContextID string) string {
|
|||||||
return perm
|
return perm
|
||||||
}
|
}
|
||||||
|
|
||||||
func existsPerm(existing []string, perm string) bool {
|
func ExistsPerm(existing []string, perm string) bool {
|
||||||
for _, e := range existing {
|
for _, e := range existing {
|
||||||
if e == perm {
|
if e == perm {
|
||||||
return true
|
return true
|
||||||
|
@ -12,15 +12,15 @@ func getTestCtx(userID, orgID string) context.Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testVerifier struct {
|
type testVerifier struct {
|
||||||
grants []*Grant
|
grant *Grant
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
||||||
return "userID", "clientID", "agentID", nil
|
return "userID", "clientID", "agentID", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {
|
func (v *testVerifier) ResolveGrant(ctx context.Context) (*Grant, error) {
|
||||||
return v.grants, nil
|
return v.grant, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||||
@ -57,8 +57,8 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
name: "Empty Context",
|
name: "Empty Context",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: getTestCtx("", ""),
|
ctx: getTestCtx("", ""),
|
||||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
verifier: &testVerifier{grant: &Grant{
|
||||||
Roles: []string{"ORG_OWNER"}}}},
|
Roles: []string{"ORG_OWNER"}}},
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
@ -81,7 +81,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
name: "No Grants",
|
name: "No Grants",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: getTestCtx("", ""),
|
ctx: getTestCtx("", ""),
|
||||||
verifier: &testVerifier{grants: []*Grant{}},
|
verifier: &testVerifier{grant: &Grant{}},
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
@ -102,8 +102,8 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
name: "Get Permissions",
|
name: "Get Permissions",
|
||||||
args: args{
|
args: args{
|
||||||
ctx: getTestCtx("userID", "orgID"),
|
ctx: getTestCtx("userID", "orgID"),
|
||||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
verifier: &testVerifier{grant: &Grant{
|
||||||
Roles: []string{"ORG_OWNER"}}}},
|
Roles: []string{"ORG_OWNER"}}},
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
@ -143,7 +143,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
|
|||||||
func Test_MapGrantsToPermissions(t *testing.T) {
|
func Test_MapGrantsToPermissions(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
requiredPerm string
|
requiredPerm string
|
||||||
grants []*Grant
|
grant *Grant
|
||||||
authConfig *Config
|
authConfig *Config
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -155,8 +155,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
|||||||
name: "One Role existing perm",
|
name: "One Role existing perm",
|
||||||
args: args{
|
args: args{
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
grants: []*Grant{&Grant{
|
grant: &Grant{Roles: []string{"ORG_OWNER"}},
|
||||||
Roles: []string{"ORG_OWNER"}}},
|
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
RoleMapping{
|
RoleMapping{
|
||||||
@ -176,8 +175,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
|||||||
name: "One Role not existing perm",
|
name: "One Role not existing perm",
|
||||||
args: args{
|
args: args{
|
||||||
requiredPerm: "project.write",
|
requiredPerm: "project.write",
|
||||||
grants: []*Grant{&Grant{
|
grant: &Grant{Roles: []string{"ORG_OWNER"}},
|
||||||
Roles: []string{"ORG_OWNER"}}},
|
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
RoleMapping{
|
RoleMapping{
|
||||||
@ -197,8 +195,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
|||||||
name: "Multiple Roles one existing",
|
name: "Multiple Roles one existing",
|
||||||
args: args{
|
args: args{
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
grants: []*Grant{&Grant{
|
grant: &Grant{Roles: []string{"ORG_OWNER", "IAM_OWNER"}},
|
||||||
Roles: []string{"ORG_OWNER", "IAM_OWNER"}}},
|
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
RoleMapping{
|
RoleMapping{
|
||||||
@ -218,8 +215,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
|||||||
name: "Multiple Roles, global and specific",
|
name: "Multiple Roles, global and specific",
|
||||||
args: args{
|
args: args{
|
||||||
requiredPerm: "project.read",
|
requiredPerm: "project.read",
|
||||||
grants: []*Grant{&Grant{
|
grant: &Grant{Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}},
|
||||||
Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}}},
|
|
||||||
authConfig: &Config{
|
authConfig: &Config{
|
||||||
RolePermissionMappings: []RoleMapping{
|
RolePermissionMappings: []RoleMapping{
|
||||||
RoleMapping{
|
RoleMapping{
|
||||||
@ -238,7 +234,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := mapGrantsToPermissions(tt.args.requiredPerm, tt.args.grants, tt.args.authConfig)
|
result := mapGrantToPermissions(tt.args.requiredPerm, tt.args.grant, tt.args.authConfig)
|
||||||
if !equalStringArray(result, tt.result) {
|
if !equalStringArray(result, tt.result) {
|
||||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||||
}
|
}
|
||||||
@ -419,7 +415,7 @@ func Test_ExistisPerm(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result := existsPerm(tt.args.existing, tt.args.perm)
|
result := ExistsPerm(tt.args.existing, tt.args.perm)
|
||||||
if result != tt.result {
|
if result != tt.result {
|
||||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ const (
|
|||||||
ContentType = "content-type"
|
ContentType = "content-type"
|
||||||
Location = "location"
|
Location = "location"
|
||||||
Origin = "origin"
|
Origin = "origin"
|
||||||
|
UserAgent = "user-agent"
|
||||||
|
ForwardedFor = "x-forwarded-for"
|
||||||
|
|
||||||
ZitadelOrgID = "x-zitadel-orgid"
|
ZitadelOrgID = "x-zitadel-orgid"
|
||||||
//TODO: Remove as soon an authentification is implemented
|
//TODO: Remove as soon an authentification is implemented
|
||||||
|
71
internal/api/http/header.go
Normal file
71
internal/api/http/header.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpHeaders key
|
||||||
|
remoteAddr key
|
||||||
|
)
|
||||||
|
|
||||||
|
func CopyHeadersToContext(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := context.WithValue(r.Context(), httpHeaders, r.Header)
|
||||||
|
ctx = context.WithValue(ctx, remoteAddr, r.RemoteAddr)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
h(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HeadersFromCtx(ctx context.Context) (http.Header, bool) {
|
||||||
|
headers, ok := ctx.Value(httpHeaders).(http.Header)
|
||||||
|
return headers, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoteIPFromCtx(ctx context.Context) string {
|
||||||
|
ctxHeaders, ok := HeadersFromCtx(ctx)
|
||||||
|
if !ok {
|
||||||
|
return RemoteAddrFromCtx(ctx)
|
||||||
|
}
|
||||||
|
forwarded, ok := ForwardedFor(ctxHeaders)
|
||||||
|
if ok {
|
||||||
|
return forwarded
|
||||||
|
}
|
||||||
|
return RemoteAddrFromCtx(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoteIPFromRequest(r *http.Request) net.IP {
|
||||||
|
return net.ParseIP(RemoteIPStringFromRequest(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoteIPStringFromRequest(r *http.Request) string {
|
||||||
|
ip, ok := ForwardedFor(r.Header)
|
||||||
|
if ok {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func ForwardedFor(headers http.Header) (string, bool) {
|
||||||
|
forwarded, ok := headers[api.ForwardedFor]
|
||||||
|
if ok {
|
||||||
|
ip := strings.Split(forwarded[0], ", ")[0]
|
||||||
|
if ip != "" {
|
||||||
|
return ip, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoteAddrFromCtx(ctx context.Context) string {
|
||||||
|
ctxRemoteAddr, _ := ctx.Value(remoteAddr).(string)
|
||||||
|
return ctxRemoteAddr
|
||||||
|
}
|
68
internal/api/http/user_agent_cookie.go
Normal file
68
internal/api/http/user_agent_cookie.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserAgent struct {
|
||||||
|
ID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAgentHandler struct {
|
||||||
|
handler *CookieHandler
|
||||||
|
cookieName string
|
||||||
|
idGenerator id.Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserAgentCookieConfig struct {
|
||||||
|
Name string
|
||||||
|
Domain string
|
||||||
|
Key *crypto.KeyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator) (*UserAgentHandler, error) {
|
||||||
|
keys, _, err := crypto.LoadKeys(config.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cookieKey := []byte(keys[config.Key.EncryptionKeyID])
|
||||||
|
handler := NewCookieHandler(
|
||||||
|
WithEncryption(cookieKey, cookieKey),
|
||||||
|
WithDomain(config.Domain),
|
||||||
|
WithUnsecure(),
|
||||||
|
)
|
||||||
|
return &UserAgentHandler{
|
||||||
|
cookieName: config.Name,
|
||||||
|
handler: handler,
|
||||||
|
idGenerator: idGenerator,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ua *UserAgentHandler) NewUserAgent() (*UserAgent, error) {
|
||||||
|
agentID, err := ua.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &UserAgent{ID: agentID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ua *UserAgentHandler) GetUserAgent(r *http.Request) (*UserAgent, error) {
|
||||||
|
userAgent := new(UserAgent)
|
||||||
|
err := ua.handler.GetEncryptedCookieValue(r, ua.cookieName, userAgent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowPermissionDenied(err, "HTTP-YULqH4", "cannot read user agent cookie")
|
||||||
|
}
|
||||||
|
return userAgent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ua *UserAgentHandler) SetUserAgent(w http.ResponseWriter, agent *UserAgent) error {
|
||||||
|
err := ua.handler.SetEncryptedCookie(w, ua.cookieName, agent)
|
||||||
|
if err != nil {
|
||||||
|
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,23 +3,34 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/caos/zitadel/internal/api/auth"
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
authName = "Auth-API"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TokenVerifier struct {
|
type TokenVerifier struct {
|
||||||
|
authID string
|
||||||
|
authZRepo *authz_repo.EsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start() (v *TokenVerifier) {
|
func Start(authZRepo *authz_repo.EsRepository) (v *TokenVerifier) {
|
||||||
return new(TokenVerifier)
|
return &TokenVerifier{authZRepo: authZRepo}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
func (v *TokenVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
||||||
return "", "", "", nil
|
userID, clientID, agentID, err := v.authZRepo.VerifyAccessToken(ctx, token, authName, v.authID)
|
||||||
|
if clientID != "" {
|
||||||
|
v.authID = clientID
|
||||||
|
}
|
||||||
|
return userID, clientID, agentID, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) ResolveGrants(ctx context.Context, userID, orgID string) ([]*auth.Grant, error) {
|
func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*auth.Grant, error) {
|
||||||
return nil, nil
|
return v.authZRepo.ResolveGrants(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||||
return "", nil
|
return v.authZRepo.ProjectIDByClientID(ctx, clientID)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
package auth
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
}
|
|
12
internal/auth/repository/application.go
Normal file
12
internal/auth/repository/application.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/project/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApplicationRepository interface {
|
||||||
|
ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error)
|
||||||
|
AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error
|
||||||
|
}
|
@ -9,7 +9,12 @@ import (
|
|||||||
type AuthRequestRepository interface {
|
type AuthRequestRepository interface {
|
||||||
CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error)
|
CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*model.AuthRequest, error)
|
||||||
AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
|
AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
|
||||||
|
AuthRequestByIDCheckLoggedIn(ctx context.Context, id string) (*model.AuthRequest, error)
|
||||||
|
AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error)
|
||||||
|
SaveAuthCode(ctx context.Context, id, code string) error
|
||||||
|
DeleteAuthRequest(ctx context.Context, id string) error
|
||||||
CheckUsername(ctx context.Context, id, username string) error
|
CheckUsername(ctx context.Context, id, username string) error
|
||||||
|
SelectUser(ctx context.Context, id, userID string) error
|
||||||
VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error
|
VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error
|
||||||
VerifyMfaOTP(ctx context.Context, agentID, authRequestID string, code string, info *model.BrowserInfo) error
|
VerifyMfaOTP(ctx context.Context, agentID, authRequestID string, code string, info *model.BrowserInfo) error
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
"github.com/caos/zitadel/internal/project/model"
|
||||||
|
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApplicationRepo struct {
|
||||||
|
View *view.View
|
||||||
|
ProjectEvents *proj_event.ProjectEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApplicationRepo) ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error) {
|
||||||
|
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return proj_view_model.ApplicationViewToModel(app), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error {
|
||||||
|
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.ProjectEvents.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||||
|
}
|
@ -4,23 +4,28 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
cache "github.com/caos/zitadel/internal/auth_request/repository"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
user_model "github.com/caos/zitadel/internal/user/model"
|
user_model "github.com/caos/zitadel/internal/user/model"
|
||||||
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
|
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthRequestRepo struct {
|
type AuthRequestRepo struct {
|
||||||
UserEvents *user_event.UserEventstore
|
UserEvents *user_event.UserEventstore
|
||||||
AuthRequests *cache.AuthRequestCache
|
AuthRequests cache.AuthRequestCache
|
||||||
View *view.View
|
View *view.View
|
||||||
|
|
||||||
UserSessionViewProvider userSessionViewProvider
|
UserSessionViewProvider userSessionViewProvider
|
||||||
UserViewProvider userViewProvider
|
UserViewProvider userViewProvider
|
||||||
|
UserEventProvider userEventProvider
|
||||||
|
|
||||||
IdGenerator id.Generator
|
IdGenerator id.Generator
|
||||||
|
|
||||||
@ -38,6 +43,10 @@ type userViewProvider interface {
|
|||||||
UserByID(string) (*view_model.UserView, error)
|
UserByID(string) (*view_model.UserView, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userEventProvider interface {
|
||||||
|
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
|
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
|
||||||
if err := repo.UserEvents.Health(ctx); err != nil {
|
if err := repo.UserEvents.Health(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -51,6 +60,11 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
request.ID = reqID
|
request.ID = reqID
|
||||||
|
ids, err := repo.View.AppIDsFromProjectByClientID(ctx, request.ApplicationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.Audience = ids
|
||||||
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
|
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -59,11 +73,28 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) {
|
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) {
|
||||||
|
return repo.getAuthRequest(ctx, id, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id string) (*model.AuthRequest, error) {
|
||||||
|
return repo.getAuthRequest(ctx, id, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code string) error {
|
||||||
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.Code = code
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error) {
|
||||||
|
request, err := repo.AuthRequests.GetAuthRequestByCode(ctx, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
steps, err := repo.nextSteps(request)
|
steps, err := repo.nextSteps(ctx, request, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -71,6 +102,10 @@ func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*m
|
|||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) DeleteAuthRequest(ctx context.Context, id string) error {
|
||||||
|
return repo.AuthRequests.DeleteAuthRequest(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username string) error {
|
func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username string) error {
|
||||||
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -80,8 +115,21 @@ func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username str
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
request.UserID = user.ID
|
request.SetUserInfo(user.ID, user.UserName, user.ResourceOwner)
|
||||||
return repo.AuthRequests.SaveAuthRequest(ctx, request)
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string) error {
|
||||||
|
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user, err := repo.View.UserByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.SetUserInfo(user.ID, user.UserName, user.ResourceOwner)
|
||||||
|
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error {
|
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error {
|
||||||
@ -89,8 +137,8 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, pas
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if request.UserID == userID {
|
if request.UserID != userID {
|
||||||
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id ")
|
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id")
|
||||||
}
|
}
|
||||||
return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info))
|
return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info))
|
||||||
}
|
}
|
||||||
@ -106,15 +154,29 @@ func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, us
|
|||||||
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
|
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.NextStep, error) {
|
func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, checkLoggedIn bool) (*model.AuthRequest, error) {
|
||||||
|
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
steps, err := repo.nextSteps(ctx, request, checkLoggedIn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.PossibleSteps = steps
|
||||||
|
return request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
|
||||||
if request == nil {
|
if request == nil {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
|
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
|
||||||
}
|
}
|
||||||
steps := make([]model.NextStep, 0)
|
steps := make([]model.NextStep, 0)
|
||||||
|
if !checkLoggedIn && request.Prompt == model.PromptNone {
|
||||||
|
return append(steps, &model.RedirectToCallbackStep{}), nil
|
||||||
|
}
|
||||||
if request.UserID == "" {
|
if request.UserID == "" {
|
||||||
if request.Prompt != model.PromptNone {
|
steps = append(steps, &model.LoginStep{})
|
||||||
steps = append(steps, &model.LoginStep{})
|
|
||||||
}
|
|
||||||
if request.Prompt == model.PromptSelectAccount {
|
if request.Prompt == model.PromptSelectAccount {
|
||||||
users, err := repo.usersForUserSelection(request)
|
users, err := repo.usersForUserSelection(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,15 +186,18 @@ func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.Next
|
|||||||
}
|
}
|
||||||
return steps, nil
|
return steps, nil
|
||||||
}
|
}
|
||||||
userSession, err := userSessionByIDs(repo.UserSessionViewProvider, request.AgentID, request.UserID)
|
user, err := userByID(ctx, repo.UserViewProvider, repo.UserEventProvider, request.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user, err := userByID(repo.UserViewProvider, request.UserID)
|
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.InitRequired {
|
||||||
|
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
|
||||||
|
}
|
||||||
if !user.PasswordSet {
|
if !user.PasswordSet {
|
||||||
return append(steps, &model.InitPasswordStep{}), nil
|
return append(steps, &model.InitPasswordStep{}), nil
|
||||||
}
|
}
|
||||||
@ -140,6 +205,8 @@ func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.Next
|
|||||||
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
|
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
|
||||||
return append(steps, &model.PasswordStep{}), nil
|
return append(steps, &model.PasswordStep{}), nil
|
||||||
}
|
}
|
||||||
|
request.PasswordVerified = true
|
||||||
|
request.AuthTime = userSession.PasswordVerification
|
||||||
|
|
||||||
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
|
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
|
||||||
return append(steps, step), nil
|
return append(steps, step), nil
|
||||||
@ -178,23 +245,32 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) (
|
|||||||
|
|
||||||
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool) {
|
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool) {
|
||||||
mfaLevel := request.MfaLevel()
|
mfaLevel := request.MfaLevel()
|
||||||
required := user.MfaMaxSetUp < mfaLevel
|
promptRequired := user.MfaMaxSetUp < mfaLevel
|
||||||
if required || !repo.mfaSkippedOrSetUp(user) {
|
if promptRequired || !repo.mfaSkippedOrSetUp(user) {
|
||||||
return &model.MfaPromptStep{
|
return &model.MfaPromptStep{
|
||||||
Required: required,
|
Required: promptRequired,
|
||||||
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
|
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
|
||||||
}, false
|
}, false
|
||||||
}
|
}
|
||||||
switch mfaLevel {
|
switch mfaLevel {
|
||||||
default:
|
default:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case model.MfaLevelNotSetUp:
|
||||||
|
if user.MfaMaxSetUp == model.MfaLevelNotSetUp {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
case model.MfaLevelSoftware:
|
case model.MfaLevelSoftware:
|
||||||
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
|
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
|
||||||
|
request.MfasVerified = append(request.MfasVerified, userSession.MfaSoftwareVerificationType)
|
||||||
|
request.AuthTime = userSession.MfaSoftwareVerification
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
case model.MfaLevelHardware:
|
case model.MfaLevelHardware:
|
||||||
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
|
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
|
||||||
|
request.MfasVerified = append(request.MfasVerified, userSession.MfaHardwareVerificationType)
|
||||||
|
request.AuthTime = userSession.MfaHardwareVerification
|
||||||
return nil, true
|
return nil, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +280,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
|
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
|
||||||
if user.MfaMaxSetUp >= 0 {
|
if user.MfaMaxSetUp > model.MfaLevelNotSetUp {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
|
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
|
||||||
@ -222,18 +298,55 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string)
|
|||||||
return view_model.UserSessionsToModel(session), nil
|
return view_model.UserSessionsToModel(session), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func userSessionByIDs(provider userSessionViewProvider, agentID, userID string) (*user_model.UserSessionView, error) {
|
func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eventProvider userEventProvider, agentID string, user *user_model.UserView) (*user_model.UserSessionView, error) {
|
||||||
session, err := provider.UserSessionByIDs(agentID, userID)
|
session, err := provider.UserSessionByIDs(agentID, user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if !errors.IsNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
session = &view_model.UserSessionView{}
|
||||||
}
|
}
|
||||||
return view_model.UserSessionToModel(session), nil
|
events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events")
|
||||||
|
return view_model.UserSessionToModel(session), nil
|
||||||
|
}
|
||||||
|
sessionCopy := *session
|
||||||
|
for _, event := range events {
|
||||||
|
switch event.Type {
|
||||||
|
case es_model.UserPasswordCheckSucceeded,
|
||||||
|
es_model.UserPasswordCheckFailed,
|
||||||
|
es_model.MfaOtpCheckSucceeded,
|
||||||
|
es_model.MfaOtpCheckFailed:
|
||||||
|
eventData, err := view_model.UserSessionFromEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data")
|
||||||
|
return view_model.UserSessionToModel(session), nil
|
||||||
|
}
|
||||||
|
if eventData.UserAgentID != agentID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sessionCopy.AppendEvent(event)
|
||||||
|
}
|
||||||
|
return view_model.UserSessionToModel(&sessionCopy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func userByID(provider userViewProvider, userID string) (*user_model.UserView, error) {
|
func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) {
|
||||||
user, err := provider.UserByID(userID)
|
user, err := viewProvider.UserByID(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return view_model.UserToModel(user), nil
|
events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events")
|
||||||
|
return view_model.UserToModel(user), nil
|
||||||
|
}
|
||||||
|
userCopy := *user
|
||||||
|
for _, event := range events {
|
||||||
|
if err := userCopy.AppendEvent(event); err != nil {
|
||||||
|
return view_model.UserToModel(user), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return view_model.UserToModel(&userCopy), nil
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eventstore
|
package eventstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -10,8 +12,10 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
user_model "github.com/caos/zitadel/internal/user/model"
|
user_model "github.com/caos/zitadel/internal/user/model"
|
||||||
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
|
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -25,6 +29,16 @@ func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.Use
|
|||||||
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockViewErrUserSession struct{}
|
||||||
|
|
||||||
|
func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
|
||||||
|
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
|
||||||
|
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
type mockViewUserSession struct {
|
type mockViewUserSession struct {
|
||||||
PasswordVerification time.Time
|
PasswordVerification time.Time
|
||||||
MfaSoftwareVerification time.Time
|
MfaSoftwareVerification time.Time
|
||||||
@ -60,7 +74,26 @@ func (m *mockViewNoUser) UserByID(string) (*view_model.UserView, error) {
|
|||||||
return nil, errors.ThrowNotFound(nil, "id", "user not found")
|
return nil, errors.ThrowNotFound(nil, "id", "user not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockEventUser struct {
|
||||||
|
Event *es_models.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
||||||
|
events := make([]*es_models.Event, 0)
|
||||||
|
if m.Event != nil {
|
||||||
|
events = append(events, m.Event)
|
||||||
|
}
|
||||||
|
return events, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockEventErrUser struct{}
|
||||||
|
|
||||||
|
func (m *mockEventErrUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
||||||
|
return nil, errors.ThrowInternal(nil, "id", "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
type mockViewUser struct {
|
type mockViewUser struct {
|
||||||
|
InitRequired bool
|
||||||
PasswordSet bool
|
PasswordSet bool
|
||||||
PasswordChangeRequired bool
|
PasswordChangeRequired bool
|
||||||
IsEmailVerified bool
|
IsEmailVerified bool
|
||||||
@ -71,6 +104,7 @@ type mockViewUser struct {
|
|||||||
|
|
||||||
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
|
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
|
||||||
return &view_model.UserView{
|
return &view_model.UserView{
|
||||||
|
InitRequired: m.InitRequired,
|
||||||
PasswordSet: m.PasswordSet,
|
PasswordSet: m.PasswordSet,
|
||||||
PasswordChangeRequired: m.PasswordChangeRequired,
|
PasswordChangeRequired: m.PasswordChangeRequired,
|
||||||
IsEmailVerified: m.IsEmailVerified,
|
IsEmailVerified: m.IsEmailVerified,
|
||||||
@ -87,13 +121,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
View *view.View
|
View *view.View
|
||||||
userSessionViewProvider userSessionViewProvider
|
userSessionViewProvider userSessionViewProvider
|
||||||
userViewProvider userViewProvider
|
userViewProvider userViewProvider
|
||||||
|
userEventProvider userEventProvider
|
||||||
PasswordCheckLifeTime time.Duration
|
PasswordCheckLifeTime time.Duration
|
||||||
MfaInitSkippedLifeTime time.Duration
|
MfaInitSkippedLifeTime time.Duration
|
||||||
MfaSoftwareCheckLifeTime time.Duration
|
MfaSoftwareCheckLifeTime time.Duration
|
||||||
MfaHardwareCheckLifeTime time.Duration
|
MfaHardwareCheckLifeTime time.Duration
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
request *model.AuthRequest
|
request *model.AuthRequest
|
||||||
|
checkLoggedIn bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -105,22 +141,22 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"request nil, error",
|
"request nil, error",
|
||||||
fields{},
|
fields{},
|
||||||
args{nil},
|
args{nil, false},
|
||||||
nil,
|
nil,
|
||||||
errors.IsErrorInvalidArgument,
|
errors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user not set, login step",
|
"prompt none and checkLoggedIn false, callback step",
|
||||||
fields{},
|
fields{},
|
||||||
args{&model.AuthRequest{}},
|
args{&model.AuthRequest{Prompt: model.PromptNone}, false},
|
||||||
[]model.NextStep{&model.LoginStep{}},
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user not set and prompt none, no step",
|
"user not set, login step",
|
||||||
fields{},
|
fields{},
|
||||||
args{&model.AuthRequest{Prompt: model.PromptNone}},
|
args{&model.AuthRequest{}, false},
|
||||||
[]model.NextStep{},
|
[]model.NextStep{&model.LoginStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -128,7 +164,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
fields{
|
fields{
|
||||||
userSessionViewProvider: &mockViewNoUserSession{},
|
userSessionViewProvider: &mockViewNoUserSession{},
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
|
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false},
|
||||||
nil,
|
nil,
|
||||||
errors.IsInternal,
|
errors.IsInternal,
|
||||||
},
|
},
|
||||||
@ -147,8 +183,9 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
|
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false},
|
||||||
[]model.NextStep{
|
[]model.NextStep{
|
||||||
&model.LoginStep{},
|
&model.LoginStep{},
|
||||||
&model.SelectUserStep{
|
&model.SelectUserStep{
|
||||||
@ -166,31 +203,63 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"usersession not found, not found error",
|
"user not not found, not found error",
|
||||||
fields{
|
fields{
|
||||||
userSessionViewProvider: &mockViewNoUserSession{},
|
userViewProvider: &mockViewNoUser{},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
nil,
|
nil,
|
||||||
errors.IsNotFound,
|
errors.IsNotFound,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"user not not found, not found error",
|
"usersession not found, new user session, password step",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewNoUserSession{},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
PasswordSet: true,
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
|
[]model.NextStep{&model.PasswordStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"usersession error, internal error",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewErrUserSession{},
|
||||||
|
userViewProvider: &mockViewUser{},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
|
nil,
|
||||||
|
errors.IsInternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user not initialized, init user step",
|
||||||
fields{
|
fields{
|
||||||
userSessionViewProvider: &mockViewUserSession{},
|
userSessionViewProvider: &mockViewUserSession{},
|
||||||
userViewProvider: &mockViewNoUser{},
|
userViewProvider: &mockViewUser{
|
||||||
|
InitRequired: true,
|
||||||
|
PasswordSet: true,
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
|
[]model.NextStep{&model.InitUserStep{
|
||||||
|
PasswordSet: true,
|
||||||
|
}},
|
||||||
nil,
|
nil,
|
||||||
errors.IsNotFound,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"password not set, init password step",
|
"password not set, init password step",
|
||||||
fields{
|
fields{
|
||||||
userSessionViewProvider: &mockViewUserSession{},
|
userSessionViewProvider: &mockViewUserSession{},
|
||||||
userViewProvider: &mockViewUser{},
|
userViewProvider: &mockViewUser{},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.InitPasswordStep{}},
|
[]model.NextStep{&model.InitPasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -201,9 +270,10 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
userViewProvider: &mockViewUser{
|
userViewProvider: &mockViewUser{
|
||||||
PasswordSet: true,
|
PasswordSet: true,
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.PasswordStep{}},
|
[]model.NextStep{&model.PasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -218,10 +288,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
OTPState: int32(user_model.MFASTATE_READY),
|
OTPState: int32(user_model.MFASTATE_READY),
|
||||||
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.MfaVerificationStep{
|
[]model.NextStep{&model.MfaVerificationStep{
|
||||||
MfaProviders: []model.MfaType{model.MfaTypeOTP},
|
MfaProviders: []model.MfaType{model.MfaTypeOTP},
|
||||||
}},
|
}},
|
||||||
@ -238,11 +309,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
PasswordSet: true,
|
PasswordSet: true,
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
IsEmailVerified: true,
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.ChangePasswordStep{}},
|
[]model.NextStep{&model.ChangePasswordStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -255,11 +328,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
},
|
},
|
||||||
userViewProvider: &mockViewUser{
|
userViewProvider: &mockViewUser{
|
||||||
PasswordSet: true,
|
PasswordSet: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.VerifyEMailStep{}},
|
[]model.NextStep{&model.VerifyEMailStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -273,11 +348,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
userViewProvider: &mockViewUser{
|
userViewProvider: &mockViewUser{
|
||||||
PasswordSet: true,
|
PasswordSet: true,
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
|
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -291,11 +368,33 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
userViewProvider: &mockViewUser{
|
userViewProvider: &mockViewUser{
|
||||||
PasswordSet: true,
|
PasswordSet: true,
|
||||||
IsEmailVerified: true,
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
},
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
},
|
},
|
||||||
args{&model.AuthRequest{UserID: "UserID"}},
|
args{&model.AuthRequest{UserID: "UserID"}, false},
|
||||||
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"prompt none, checkLoggedIn true and authenticated, redirect to callback step",
|
||||||
|
fields{
|
||||||
|
userSessionViewProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
|
||||||
|
},
|
||||||
|
userViewProvider: &mockViewUser{
|
||||||
|
PasswordSet: true,
|
||||||
|
IsEmailVerified: true,
|
||||||
|
MfaMaxSetUp: int32(model.MfaLevelSoftware),
|
||||||
|
},
|
||||||
|
userEventProvider: &mockEventUser{},
|
||||||
|
PasswordCheckLifeTime: 10 * 24 * time.Hour,
|
||||||
|
MfaSoftwareCheckLifeTime: 18 * time.Hour,
|
||||||
|
},
|
||||||
|
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone}, true},
|
||||||
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
[]model.NextStep{&model.RedirectToCallbackStep{}},
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
@ -308,12 +407,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
|
|||||||
View: tt.fields.View,
|
View: tt.fields.View,
|
||||||
UserSessionViewProvider: tt.fields.userSessionViewProvider,
|
UserSessionViewProvider: tt.fields.userSessionViewProvider,
|
||||||
UserViewProvider: tt.fields.userViewProvider,
|
UserViewProvider: tt.fields.userViewProvider,
|
||||||
|
UserEventProvider: tt.fields.userEventProvider,
|
||||||
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
|
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
|
||||||
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
|
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
|
||||||
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
|
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
|
||||||
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
|
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
|
||||||
}
|
}
|
||||||
got, err := repo.nextSteps(tt.args.request)
|
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
|
||||||
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
|
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
|
||||||
t.Errorf("nextSteps() wrong error = %v", err)
|
t.Errorf("nextSteps() wrong error = %v", err)
|
||||||
return
|
return
|
||||||
@ -360,14 +460,31 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
request: &model.AuthRequest{},
|
request: &model.AuthRequest{},
|
||||||
user: &user_model.UserView{
|
user: &user_model.UserView{
|
||||||
MfaMaxSetUp: -1,
|
MfaMaxSetUp: model.MfaLevelNotSetUp,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&model.MfaPromptStep{
|
&model.MfaPromptStep{
|
||||||
MfaProviders: []model.MfaType{},
|
MfaProviders: []model.MfaType{
|
||||||
|
model.MfaTypeOTP,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"not set up and skipped, true",
|
||||||
|
fields{
|
||||||
|
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
request: &model.AuthRequest{},
|
||||||
|
user: &user_model.UserView{
|
||||||
|
MfaMaxSetUp: model.MfaLevelNotSetUp,
|
||||||
|
MfaInitSkipped: time.Now().UTC(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checked mfa software, true",
|
"checked mfa software, true",
|
||||||
fields{
|
fields{
|
||||||
@ -376,7 +493,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
request: &model.AuthRequest{},
|
request: &model.AuthRequest{},
|
||||||
user: &user_model.UserView{
|
user: &user_model.UserView{
|
||||||
OTPState: user_model.MFASTATE_READY,
|
MfaMaxSetUp: model.MfaLevelSoftware,
|
||||||
|
OTPState: user_model.MFASTATE_READY,
|
||||||
},
|
},
|
||||||
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
|
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
|
||||||
},
|
},
|
||||||
@ -391,7 +509,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
|
|||||||
args{
|
args{
|
||||||
request: &model.AuthRequest{},
|
request: &model.AuthRequest{},
|
||||||
user: &user_model.UserView{
|
user: &user_model.UserView{
|
||||||
OTPState: user_model.MFASTATE_READY,
|
MfaMaxSetUp: model.MfaLevelSoftware,
|
||||||
|
OTPState: user_model.MFASTATE_READY,
|
||||||
},
|
},
|
||||||
userSession: &user_model.UserSessionView{},
|
userSession: &user_model.UserSessionView{},
|
||||||
},
|
},
|
||||||
@ -473,3 +592,235 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_userSessionByIDs(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
userProvider userSessionViewProvider
|
||||||
|
eventProvider userEventProvider
|
||||||
|
agentID string
|
||||||
|
user *user_model.UserView
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *user_model.UserSessionView
|
||||||
|
wantErr func(error) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"not found, new session",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewNoUserSession{},
|
||||||
|
eventProvider: &mockEventErrUser{},
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
},
|
||||||
|
&user_model.UserSessionView{},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"internal error, internal error",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewErrUserSession{},
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
errors.IsInternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"error user events, old view model state",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
eventProvider: &mockEventErrUser{},
|
||||||
|
},
|
||||||
|
&user_model.UserSessionView{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
MfaSoftwareVerification: time.Time{},
|
||||||
|
MfaHardwareVerification: time.Time{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new user events but error, old view model state",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
agentID: "agentID",
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
eventProvider: &mockEventUser{
|
||||||
|
&es_models.Event{
|
||||||
|
AggregateType: user_es_model.UserAggregate,
|
||||||
|
Type: user_es_model.MfaOtpCheckSucceeded,
|
||||||
|
CreationDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&user_model.UserSessionView{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
MfaSoftwareVerification: time.Time{},
|
||||||
|
MfaHardwareVerification: time.Time{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new user events but other agentID, old view model state",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
agentID: "agentID",
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
eventProvider: &mockEventUser{
|
||||||
|
&es_models.Event{
|
||||||
|
AggregateType: user_es_model.UserAggregate,
|
||||||
|
Type: user_es_model.MfaOtpCheckSucceeded,
|
||||||
|
CreationDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
Data: func() []byte {
|
||||||
|
data, _ := json.Marshal(&user_es_model.AuthRequest{UserAgentID: "otherID"})
|
||||||
|
return data
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&user_model.UserSessionView{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
MfaSoftwareVerification: time.Time{},
|
||||||
|
MfaHardwareVerification: time.Time{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new user events, new view model state",
|
||||||
|
args{
|
||||||
|
userProvider: &mockViewUserSession{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
agentID: "agentID",
|
||||||
|
user: &user_model.UserView{ID: "id"},
|
||||||
|
eventProvider: &mockEventUser{
|
||||||
|
&es_models.Event{
|
||||||
|
AggregateType: user_es_model.UserAggregate,
|
||||||
|
Type: user_es_model.MfaOtpCheckSucceeded,
|
||||||
|
CreationDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
Data: func() []byte {
|
||||||
|
data, _ := json.Marshal(&user_es_model.AuthRequest{UserAgentID: "agentID"})
|
||||||
|
return data
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&user_model.UserSessionView{
|
||||||
|
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
MfaSoftwareVerification: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
ChangeDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := userSessionByIDs(context.Background(), tt.args.userProvider, tt.args.eventProvider, tt.args.agentID, tt.args.user)
|
||||||
|
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
|
||||||
|
t.Errorf("nextSteps() wrong error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_userByID(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
ctx context.Context
|
||||||
|
viewProvider userViewProvider
|
||||||
|
eventProvider userEventProvider
|
||||||
|
userID string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want *user_model.UserView
|
||||||
|
wantErr func(error) bool
|
||||||
|
}{
|
||||||
|
|
||||||
|
{
|
||||||
|
"not found, not found error",
|
||||||
|
args{
|
||||||
|
viewProvider: &mockViewNoUser{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
errors.IsNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"error user events, old view model state",
|
||||||
|
args{
|
||||||
|
viewProvider: &mockViewUser{
|
||||||
|
PasswordChangeRequired: true,
|
||||||
|
},
|
||||||
|
eventProvider: &mockEventErrUser{},
|
||||||
|
},
|
||||||
|
&user_model.UserView{
|
||||||
|
PasswordChangeRequired: true,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new user events but error, old view model state",
|
||||||
|
args{
|
||||||
|
viewProvider: &mockViewUser{
|
||||||
|
PasswordChangeRequired: true,
|
||||||
|
},
|
||||||
|
eventProvider: &mockEventUser{
|
||||||
|
&es_models.Event{
|
||||||
|
AggregateType: user_es_model.UserAggregate,
|
||||||
|
Type: user_es_model.UserPasswordChanged,
|
||||||
|
CreationDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&user_model.UserView{
|
||||||
|
PasswordChangeRequired: true,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"new user events, new view model state",
|
||||||
|
args{
|
||||||
|
viewProvider: &mockViewUser{
|
||||||
|
PasswordChangeRequired: true,
|
||||||
|
},
|
||||||
|
eventProvider: &mockEventUser{
|
||||||
|
&es_models.Event{
|
||||||
|
AggregateType: user_es_model.UserAggregate,
|
||||||
|
Type: user_es_model.UserPasswordChanged,
|
||||||
|
CreationDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
Data: func() []byte {
|
||||||
|
data, _ := json.Marshal(user_es_model.Password{ChangeRequired: false})
|
||||||
|
return data
|
||||||
|
}(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&user_model.UserView{
|
||||||
|
PasswordChangeRequired: false,
|
||||||
|
ChangeDate: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
State: user_model.USERSTATE_INITIAL,
|
||||||
|
PasswordChanged: time.Now().UTC().Round(1 * time.Second),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := userByID(tt.args.ctx, tt.args.viewProvider, tt.args.eventProvider, tt.args.userID)
|
||||||
|
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
|
||||||
|
t.Errorf("nextSteps() wrong error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
16
internal/auth/repository/eventsourcing/eventstore/iam.go
Normal file
16
internal/auth/repository/eventsourcing/eventstore/iam.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamRepository struct {
|
||||||
|
IamID string
|
||||||
|
IamEvents *iam_event.IamEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IamRepository) GetIam(ctx context.Context) (*model.Iam, error) {
|
||||||
|
return repo.IamEvents.IamByID(ctx, repo.IamID)
|
||||||
|
}
|
75
internal/auth/repository/eventsourcing/eventstore/key.go
Normal file
75
internal/auth/repository/eventsourcing/eventstore/key.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
"github.com/caos/zitadel/internal/key/model"
|
||||||
|
key_event "github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
oidcUser = "OIDC"
|
||||||
|
iamOrg = "IAM"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyRepository struct {
|
||||||
|
KeyEvents *key_event.KeyEventstore
|
||||||
|
View *view.View
|
||||||
|
SigningKeyRotation time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRepository) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||||
|
ctx = setOIDCCtx(ctx)
|
||||||
|
_, err := k.KeyEvents.GenerateKeyPair(ctx, model.KeyUsageSigning, algorithm)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRepository) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, renewTimer <-chan time.Time) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-renewTimer:
|
||||||
|
k.refreshSigningKey(keyCh, errCh)
|
||||||
|
renewTimer = time.After(k.SigningKeyRotation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRepository) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) {
|
||||||
|
keys, err := k.View.GetActiveKeySet()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
webKeys := make([]jose.JSONWebKey, len(keys))
|
||||||
|
for i, key := range keys {
|
||||||
|
webKeys[i] = jose.JSONWebKey{KeyID: key.ID, Algorithm: key.Algorithm, Use: key.Usage.String(), Key: key.Key}
|
||||||
|
}
|
||||||
|
return &jose.JSONWebKeySet{Keys: webKeys}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyRepository) refreshSigningKey(keyCh chan<- jose.SigningKey, errCh chan<- error) {
|
||||||
|
key, err := k.View.GetSigningKey()
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
keyCh <- jose.SigningKey{
|
||||||
|
Algorithm: jose.SignatureAlgorithm(key.Algorithm),
|
||||||
|
Key: jose.JSONWebKey{
|
||||||
|
KeyID: key.ID,
|
||||||
|
Key: key.Key,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setOIDCCtx(ctx context.Context) context.Context {
|
||||||
|
return auth.SetCtxData(ctx, auth.CtxData{UserID: oidcUser, OrgID: iamOrg})
|
||||||
|
}
|
29
internal/auth/repository/eventsourcing/eventstore/org.go
Normal file
29
internal/auth/repository/eventsourcing/eventstore/org.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrgRepository struct {
|
||||||
|
SearchLimit uint64
|
||||||
|
*org_es.OrgEventstore
|
||||||
|
View *auth_view.View
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) {
|
||||||
|
request.EnsureLimit(repo.SearchLimit)
|
||||||
|
members, count, err := repo.View.SearchOrgs(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &org_model.OrgSearchResult{
|
||||||
|
Offset: request.Offset,
|
||||||
|
Limit: request.Limit,
|
||||||
|
TotalResult: uint64(count),
|
||||||
|
Result: view.OrgsToModel(members),
|
||||||
|
}, nil
|
||||||
|
}
|
@ -13,8 +13,8 @@ type TokenRepo struct {
|
|||||||
View *view.View
|
View *view.View
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*token_model.Token, error) {
|
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*token_model.Token, error) {
|
||||||
token, err := repo.View.CreateToken(agentID, applicationID, userID, lifetime)
|
token, err := repo.View.CreateToken(agentID, applicationID, userID, audience, scopes, lifetime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -24,3 +24,11 @@ func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID,
|
|||||||
func (repo *TokenRepo) IsTokenValid(ctx context.Context, tokenID string) (bool, error) {
|
func (repo *TokenRepo) IsTokenValid(ctx context.Context, tokenID string) (bool, error) {
|
||||||
return repo.View.IsTokenValid(tokenID)
|
return repo.View.IsTokenValid(tokenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *TokenRepo) TokenByID(ctx context.Context, tokenID string) (*token_model.Token, error) {
|
||||||
|
token, err := repo.View.TokenByID(tokenID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return token_view_model.TokenToModel(token), nil
|
||||||
|
}
|
||||||
|
@ -56,10 +56,18 @@ func (repo *UserRepo) ChangeMyEmail(ctx context.Context, email *model.Email) (*m
|
|||||||
return repo.UserEvents.ChangeEmail(ctx, email)
|
return repo.UserEvents.ChangeEmail(ctx, email)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) VerifyEmail(ctx context.Context, userID, code string) error {
|
||||||
|
return repo.UserEvents.VerifyEmail(ctx, userID, code)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) VerifyMyEmail(ctx context.Context, code string) error {
|
func (repo *UserRepo) VerifyMyEmail(ctx context.Context, code string) error {
|
||||||
return repo.UserEvents.VerifyEmail(ctx, auth.GetCtxData(ctx).UserID, code)
|
return repo.UserEvents.VerifyEmail(ctx, auth.GetCtxData(ctx).UserID, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID string) error {
|
||||||
|
return repo.UserEvents.CreateEmailVerificationCode(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error {
|
func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error {
|
||||||
return repo.UserEvents.CreateEmailVerificationCode(ctx, auth.GetCtxData(ctx).UserID)
|
return repo.UserEvents.CreateEmailVerificationCode(ctx, auth.GetCtxData(ctx).UserID)
|
||||||
}
|
}
|
||||||
@ -103,11 +111,28 @@ func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new string) error {
|
||||||
|
policy, err := repo.PolicyEvents.GetPasswordComplexityPolicy(ctx, auth.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = repo.UserEvents.ChangePassword(ctx, policy, userID, old, new)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) AddMfaOTP(ctx context.Context, userID string) (*model.OTP, error) {
|
||||||
|
return repo.UserEvents.AddOTP(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) {
|
func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) {
|
||||||
return repo.UserEvents.AddOTP(ctx, auth.GetCtxData(ctx).UserID)
|
return repo.UserEvents.AddOTP(ctx, auth.GetCtxData(ctx).UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) VerifyMyMfaOTP(ctx context.Context, code string) error {
|
func (repo *UserRepo) VerifyMfaOTPSetup(ctx context.Context, userID, code string) error {
|
||||||
|
return repo.UserEvents.CheckMfaOTPSetup(ctx, userID, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) VerifyMyMfaOTPSetup(ctx context.Context, code string) error {
|
||||||
return repo.UserEvents.CheckMfaOTPSetup(ctx, auth.GetCtxData(ctx).UserID, code)
|
return repo.UserEvents.CheckMfaOTPSetup(ctx, auth.GetCtxData(ctx).UserID, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +140,19 @@ func (repo *UserRepo) RemoveMyMfaOTP(ctx context.Context) error {
|
|||||||
return repo.UserEvents.RemoveOTP(ctx, auth.GetCtxData(ctx).UserID)
|
return repo.UserEvents.RemoveOTP(ctx, auth.GetCtxData(ctx).UserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) ResendInitVerificationMail(ctx context.Context, userID string) error {
|
||||||
|
_, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) VerifyInitCode(ctx context.Context, userID, code, password string) error {
|
||||||
|
policy, err := repo.PolicyEvents.GetPasswordComplexityPolicy(ctx, auth.GetCtxData(ctx).OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.UserEvents.VerifyInitCode(ctx, policy, userID, code, password)
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *UserRepo) SkipMfaInit(ctx context.Context, userID string) error {
|
func (repo *UserRepo) SkipMfaInit(ctx context.Context, userID string) error {
|
||||||
return repo.UserEvents.SkipMfaInit(ctx, userID)
|
return repo.UserEvents.SkipMfaInit(ctx, userID)
|
||||||
}
|
}
|
||||||
@ -139,6 +177,10 @@ func (repo *UserRepo) SignOut(ctx context.Context, agentID, userID string) error
|
|||||||
return repo.UserEvents.SignOut(ctx, agentID, userID)
|
return repo.UserEvents.SignOut(ctx, agentID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *UserRepo) UserByID(ctx context.Context, userID string) (*model.User, error) {
|
||||||
|
return repo.UserEvents.UserByID(ctx, userID)
|
||||||
|
}
|
||||||
|
|
||||||
func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error {
|
func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error {
|
||||||
if obj.AggregateID != auth.GetCtxData(ctx).UserID {
|
if obj.AggregateID != auth.GetCtxData(ctx).UserID {
|
||||||
return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user")
|
return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user")
|
||||||
|
158
internal/auth/repository/eventsourcing/eventstore/user_grant.go
Normal file
158
internal/auth/repository/eventsourcing/eventstore/user_grant.go
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
org_view "github.com/caos/zitadel/internal/org/repository/view"
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepo struct {
|
||||||
|
SearchLimit uint64
|
||||||
|
View *view.View
|
||||||
|
IamID string
|
||||||
|
Auth auth.Config
|
||||||
|
AuthZRepo *authz_repo.EsRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) SearchMyUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) {
|
||||||
|
request.EnsureLimit(repo.SearchLimit)
|
||||||
|
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_USER_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: auth.GetCtxData(ctx).UserID})
|
||||||
|
grants, count, err := repo.View.SearchUserGrants(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &grant_model.UserGrantSearchResponse{
|
||||||
|
Offset: request.Offset,
|
||||||
|
Limit: request.Limit,
|
||||||
|
TotalResult: uint64(count),
|
||||||
|
Result: model.UserGrantsToModel(grants),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
|
||||||
|
request.EnsureLimit(repo.SearchLimit)
|
||||||
|
ctxData := auth.GetCtxData(ctx)
|
||||||
|
if ctxData.ProjectID == "" {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "APP-7lqva", "Could not get ProjectID")
|
||||||
|
}
|
||||||
|
if ctxData.ProjectID == repo.AuthZRepo.IamProjectID {
|
||||||
|
isAdmin, err := repo.IsIamAdmin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isAdmin {
|
||||||
|
return repo.SearchAdminOrgs(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_PROJECT_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: ctxData.ProjectID})
|
||||||
|
|
||||||
|
grants, err := repo.SearchMyUserGrants(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return grantRespToOrgResp(grants), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]string, error) {
|
||||||
|
grant, err := repo.AuthZRepo.ResolveGrants(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
permissions := &grant_model.Permissions{Permissions: []string{}}
|
||||||
|
for _, role := range grant.Roles {
|
||||||
|
roleName, ctxID := auth.SplitPermission(role)
|
||||||
|
for _, mapping := range repo.Auth.RolePermissionMappings {
|
||||||
|
if mapping.Role == roleName {
|
||||||
|
permissions.AppendPermissions(ctxID, mapping.Permissions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return permissions.Permissions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) SearchAdminOrgs(request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
|
||||||
|
searchRequest := &org_model.OrgSearchRequest{}
|
||||||
|
if len(request.Queries) > 0 {
|
||||||
|
for _, q := range request.Queries {
|
||||||
|
if q.Key == grant_model.USERGRANTSEARCHKEY_ORG_NAME {
|
||||||
|
searchRequest.Queries = append(searchRequest.Queries, &org_model.OrgSearchQuery{Key: org_model.ORGSEARCHKEY_ORG_NAME, Method: q.Method, Value: q.Value})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orgs, count, err := repo.View.SearchOrgs(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return orgRespToOrgResp(orgs, count), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) IsIamAdmin(ctx context.Context) (bool, error) {
|
||||||
|
grantSearch := &grant_model.UserGrantSearchRequest{
|
||||||
|
Queries: []*grant_model.UserGrantSearchQuery{
|
||||||
|
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_RESOURCEOWNER, Method: global_model.SEARCHMETHOD_EQUALS, Value: repo.IamID},
|
||||||
|
}}
|
||||||
|
result, err := repo.SearchMyUserGrants(ctx, grantSearch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if result.TotalResult == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_model.ProjectOrgSearchResponse {
|
||||||
|
resp := &grant_model.ProjectOrgSearchResponse{
|
||||||
|
TotalResult: grants.TotalResult,
|
||||||
|
}
|
||||||
|
resp.Result = make([]*grant_model.Org, len(grants.Result))
|
||||||
|
for i, g := range grants.Result {
|
||||||
|
resp.Result[i] = &grant_model.Org{OrgID: g.ResourceOwner, OrgName: g.OrgName}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func orgRespToOrgResp(orgs []*org_view.OrgView, count int) *grant_model.ProjectOrgSearchResponse {
|
||||||
|
resp := &grant_model.ProjectOrgSearchResponse{
|
||||||
|
TotalResult: uint64(count),
|
||||||
|
}
|
||||||
|
resp.Result = make([]*grant_model.Org, len(orgs))
|
||||||
|
for i, o := range orgs {
|
||||||
|
resp.Result[i] = &grant_model.Org{OrgID: o.ID, OrgName: o.Name}
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeOrgAndAdminGrant(ctxData auth.CtxData, orgGrant, iamAdminGrant *model.UserGrantView) (grant *auth.Grant) {
|
||||||
|
if orgGrant != nil {
|
||||||
|
roles := orgGrant.RoleKeys
|
||||||
|
if iamAdminGrant != nil {
|
||||||
|
roles = addIamAdminRoles(roles, iamAdminGrant.RoleKeys)
|
||||||
|
}
|
||||||
|
grant = &auth.Grant{OrgID: orgGrant.ResourceOwner, Roles: roles}
|
||||||
|
} else if iamAdminGrant != nil {
|
||||||
|
grant = &auth.Grant{
|
||||||
|
OrgID: ctxData.OrgID,
|
||||||
|
Roles: iamAdminGrant.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return grant
|
||||||
|
}
|
||||||
|
|
||||||
|
func addIamAdminRoles(orgRoles, iamAdminRoles []string) []string {
|
||||||
|
result := make([]string, 0)
|
||||||
|
result = append(result, iamAdminRoles...)
|
||||||
|
for _, role := range orgRoles {
|
||||||
|
if !auth.ExistsPerm(result, role) {
|
||||||
|
result = append(result, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserSessionRepo struct {
|
||||||
|
View *view.View
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
|
||||||
|
userSessions, err := repo.View.UserSessionsByUserID(auth.GetCtxData(ctx).UserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return model.UserSessionsToModel(userSessions), nil
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||||
|
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
handler
|
||||||
|
projectEvents *proj_event.ProjectEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
applicationTable = "auth.applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Application) MinimumCycleDuration() time.Duration { return p.cycleDuration }
|
||||||
|
|
||||||
|
func (p *Application) ViewModel() string {
|
||||||
|
return applicationTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := p.view.GetLatestApplicationSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return eventsourcing.ProjectQuery(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) Process(event *models.Event) (err error) {
|
||||||
|
app := new(view_model.ApplicationView)
|
||||||
|
switch event.Type {
|
||||||
|
case es_model.ApplicationAdded:
|
||||||
|
app.AppendEvent(event)
|
||||||
|
case es_model.ApplicationChanged,
|
||||||
|
es_model.OIDCConfigAdded,
|
||||||
|
es_model.OIDCConfigChanged,
|
||||||
|
es_model.ApplicationDeactivated,
|
||||||
|
es_model.ApplicationReactivated:
|
||||||
|
err := app.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err = p.view.ApplicationByID(app.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app.AppendEvent(event)
|
||||||
|
case es_model.ApplicationRemoved:
|
||||||
|
err := app.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.view.DeleteApplication(app.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return p.view.ProcessedApplicationSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.view.PutApplication(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) OnError(event *models.Event, spoolerError error) error {
|
||||||
|
logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||||
|
return spooler.HandleError(event, spoolerError, p.view.GetLatestApplicationFailedEvent, p.view.ProcessedApplicationFailedEvent, p.view.ProcessedApplicationSequence, p.errorCountUntilSkip)
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
iam_events "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
)
|
)
|
||||||
@ -11,7 +17,7 @@ import (
|
|||||||
type Configs map[string]*Config
|
type Configs map[string]*Config
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
MinimumCycleDurationMillisecond int
|
MinimumCycleDuration types.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
@ -22,14 +28,28 @@ type handler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EventstoreRepos struct {
|
type EventstoreRepos struct {
|
||||||
UserEvents *usr_event.UserEventstore
|
UserEvents *usr_event.UserEventstore
|
||||||
|
ProjectEvents *proj_event.ProjectEventstore
|
||||||
|
OrgEvents *org_events.OrgEventstore
|
||||||
|
IamEvents *iam_events.IamEventstore
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, repos EventstoreRepos) []spooler.Handler {
|
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos, systemDefaults sd.SystemDefaults) []spooler.Handler {
|
||||||
return []spooler.Handler{
|
return []spooler.Handler{
|
||||||
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}},
|
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}},
|
||||||
&UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents},
|
&UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents},
|
||||||
&Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}},
|
&Token{handler: handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount}},
|
||||||
|
&Key{handler: handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount}},
|
||||||
|
&Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}},
|
||||||
|
&Org{handler: handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount}},
|
||||||
|
&UserGrant{
|
||||||
|
handler: handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount},
|
||||||
|
eventstore: eventstore,
|
||||||
|
userEvents: repos.UserEvents,
|
||||||
|
orgEvents: repos.OrgEvents,
|
||||||
|
projectEvents: repos.ProjectEvents,
|
||||||
|
iamEvents: repos.IamEvents,
|
||||||
|
iamID: systemDefaults.IamID},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,5 +58,5 @@ func (configs Configs) cycleDuration(viewModel string) time.Duration {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return 1 * time.Second
|
return 1 * time.Second
|
||||||
}
|
}
|
||||||
return time.Duration(c.MinimumCycleDurationMillisecond) * time.Millisecond
|
return c.MinimumCycleDuration.Duration
|
||||||
}
|
}
|
||||||
|
55
internal/auth/repository/eventsourcing/handler/key.go
Normal file
55
internal/auth/repository/eventsourcing/handler/key.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
es_model "github.com/caos/zitadel/internal/key/repository/eventsourcing/model"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
"github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||||
|
view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
handler
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyTable = "auth.keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k *Key) MinimumCycleDuration() time.Duration { return k.cycleDuration }
|
||||||
|
|
||||||
|
func (k *Key) ViewModel() string {
|
||||||
|
return keyTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := k.view.GetLatestKeySequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return eventsourcing.KeyPairQuery(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) Process(event *models.Event) error {
|
||||||
|
switch event.Type {
|
||||||
|
case es_model.KeyPairAdded:
|
||||||
|
privateKey, publicKey, err := view_model.KeysFromPairEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return k.view.PutKeys(privateKey, publicKey, event.Sequence)
|
||||||
|
default:
|
||||||
|
return k.view.ProcessedKeySequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-GHa3a", "id", event.AggregateID).WithError(err).Warn("something went wrong in key handler")
|
||||||
|
return spooler.HandleError(event, err, k.view.GetLatestKeyFailedEvent, k.view.ProcessedKeyFailedEvent, k.view.ProcessedKeySequence, k.errorCountUntilSkip)
|
||||||
|
}
|
64
internal/auth/repository/eventsourcing/handler/org.go
Normal file
64
internal/auth/repository/eventsourcing/handler/org.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
"github.com/caos/zitadel/internal/org/repository/view"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Org struct {
|
||||||
|
handler
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
orgTable = "auth.orgs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (o *Org) MinimumCycleDuration() time.Duration { return o.cycleDuration }
|
||||||
|
|
||||||
|
func (o *Org) ViewModel() string {
|
||||||
|
return orgTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Org) EventQuery() (*es_models.SearchQuery, error) {
|
||||||
|
sequence, err := o.view.GetLatestOrgSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return eventsourcing.OrgQuery(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Org) Process(event *es_models.Event) error {
|
||||||
|
org := new(view.OrgView)
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case model.OrgAdded:
|
||||||
|
org.AppendEvent(event)
|
||||||
|
case model.OrgChanged:
|
||||||
|
err := org.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
org, err = o.view.OrgByID(org.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = org.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return o.view.ProcessedOrgSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
return o.view.PutOrg(org)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Org) OnError(event *es_models.Event, spoolerErr error) error {
|
||||||
|
logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler")
|
||||||
|
return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip)
|
||||||
|
}
|
@ -54,7 +54,9 @@ func (p *User) Process(event *models.Event) (err error) {
|
|||||||
es_model.UserUnlocked,
|
es_model.UserUnlocked,
|
||||||
es_model.MfaOtpAdded,
|
es_model.MfaOtpAdded,
|
||||||
es_model.MfaOtpVerified,
|
es_model.MfaOtpVerified,
|
||||||
es_model.MfaOtpRemoved:
|
es_model.MfaOtpRemoved,
|
||||||
|
es_model.MfaInitSkipped,
|
||||||
|
es_model.UserPasswordChanged:
|
||||||
user, err = p.view.UserByID(event.AggregateID)
|
user, err = p.view.UserByID(event.AggregateID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
344
internal/auth/repository/eventsourcing/handler/user_grant.go
Normal file
344
internal/auth/repository/eventsourcing/handler/user_grant.go
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_events "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
org_model "github.com/caos/zitadel/internal/org/model"
|
||||||
|
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||||
|
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||||
|
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||||
|
usr_events "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
|
usr_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||||
|
grant_es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||||
|
view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
handler
|
||||||
|
eventstore eventstore.Eventstore
|
||||||
|
projectEvents *proj_event.ProjectEventstore
|
||||||
|
userEvents *usr_events.UserEventstore
|
||||||
|
orgEvents *org_events.OrgEventstore
|
||||||
|
iamEvents *iam_events.IamEventstore
|
||||||
|
iamID string
|
||||||
|
iamProjectID string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
userGrantTable = "auth.user_grants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *UserGrant) MinimumCycleDuration() time.Duration { return u.cycleDuration }
|
||||||
|
|
||||||
|
func (u *UserGrant) ViewModel() string {
|
||||||
|
return userGrantTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
if u.iamProjectID == "" {
|
||||||
|
err := u.setIamProjectID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sequence, err := u.view.GetLatestUserGrantSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(grant_es_model.UserGrantAggregate, iam_es_model.IamAggregate, org_es_model.OrgAggregate, usr_es_model.UserAggregate, proj_es_model.ProjectAggregate).
|
||||||
|
LatestSequenceFilter(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) Process(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case grant_es_model.UserGrantAggregate:
|
||||||
|
err = u.processUserGrant(event)
|
||||||
|
case usr_es_model.UserAggregate:
|
||||||
|
err = u.processUser(event)
|
||||||
|
case proj_es_model.ProjectAggregate:
|
||||||
|
err = u.processProject(event)
|
||||||
|
case iam_es_model.IamAggregate:
|
||||||
|
err = u.processIamMember(event, "IAM", false)
|
||||||
|
case org_es_model.OrgAggregate:
|
||||||
|
return u.processOrg(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processUserGrant(event *models.Event) (err error) {
|
||||||
|
grant := new(view_model.UserGrantView)
|
||||||
|
switch event.Type {
|
||||||
|
case grant_es_model.UserGrantAdded:
|
||||||
|
err = grant.AppendEvent(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = u.fillData(grant, event.ResourceOwner)
|
||||||
|
case grant_es_model.UserGrantChanged,
|
||||||
|
grant_es_model.UserGrantDeactivated,
|
||||||
|
grant_es_model.UserGrantReactivated:
|
||||||
|
grant, err = u.view.UserGrantByID(event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = grant.AppendEvent(event)
|
||||||
|
case grant_es_model.UserGrantRemoved:
|
||||||
|
err = u.view.DeleteUserGrant(event.AggregateID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.view.PutUserGrant(grant, grant.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processUser(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case usr_es_model.UserProfileChanged,
|
||||||
|
usr_es_model.UserEmailChanged:
|
||||||
|
grants, err := u.view.UserGrantsByUserID(event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user, err := u.userEvents.UserByID(context.Background(), event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, grant := range grants {
|
||||||
|
u.fillUserData(grant, user)
|
||||||
|
err = u.view.PutUserGrant(grant, event.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processProject(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case proj_es_model.ProjectChanged:
|
||||||
|
grants, err := u.view.UserGrantsByProjectID(event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
project, err := u.projectEvents.ProjectByID(context.Background(), event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, grant := range grants {
|
||||||
|
u.fillProjectData(grant, project)
|
||||||
|
return u.view.PutUserGrant(grant, event.Sequence)
|
||||||
|
}
|
||||||
|
case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectMemberRemoved:
|
||||||
|
member := new(proj_es_model.ProjectMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "PROJECT", true, member.UserID, member.Roles)
|
||||||
|
case proj_es_model.ProjectGrantMemberAdded, proj_es_model.ProjectGrantMemberChanged, proj_es_model.ProjectGrantMemberRemoved:
|
||||||
|
member := new(proj_es_model.ProjectGrantMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "PROJECT_GRANT", true, member.UserID, member.Roles)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processOrg(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case org_es_model.OrgMemberAdded, org_es_model.OrgMemberChanged, org_es_model.OrgMemberRemoved:
|
||||||
|
member := new(org_es_model.OrgMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "ORG", false, member.UserID, member.Roles)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processIamMember(event *models.Event, rolePrefix string, suffix bool) error {
|
||||||
|
member := new(iam_es_model.IamMember)
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.IamMemberAdded, iam_es_model.IamMemberChanged:
|
||||||
|
member.SetData(event)
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
grant = &view_model.UserGrantView{
|
||||||
|
ID: u.iamProjectID + member.UserID,
|
||||||
|
ResourceOwner: u.iamID,
|
||||||
|
OrgName: u.iamID,
|
||||||
|
OrgDomain: u.iamID,
|
||||||
|
ProjectID: u.iamProjectID,
|
||||||
|
UserID: member.UserID,
|
||||||
|
RoleKeys: member.Roles,
|
||||||
|
CreationDate: event.CreationDate,
|
||||||
|
}
|
||||||
|
if suffix {
|
||||||
|
grant.RoleKeys = suffixRoles(event.AggregateID, grant.RoleKeys)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRoles := member.Roles
|
||||||
|
if grant.RoleKeys != nil {
|
||||||
|
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||||
|
} else {
|
||||||
|
grant.RoleKeys = newRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
grant.Sequence = event.Sequence
|
||||||
|
grant.ChangeDate = event.CreationDate
|
||||||
|
return u.view.PutUserGrant(grant, grant.Sequence)
|
||||||
|
case iam_es_model.IamMemberRemoved:
|
||||||
|
member.SetData(event)
|
||||||
|
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processMember(event *models.Event, rolePrefix string, suffix bool, userID string, roleKeys []string) error {
|
||||||
|
switch event.Type {
|
||||||
|
case org_es_model.OrgMemberAdded, proj_es_model.ProjectMemberAdded, proj_es_model.ProjectGrantMemberAdded,
|
||||||
|
org_es_model.OrgMemberChanged, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectGrantMemberChanged:
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if suffix {
|
||||||
|
roleKeys = suffixRoles(event.AggregateID, roleKeys)
|
||||||
|
}
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
grant = &view_model.UserGrantView{
|
||||||
|
ID: u.iamProjectID + event.ResourceOwner + userID,
|
||||||
|
ResourceOwner: event.ResourceOwner,
|
||||||
|
ProjectID: u.iamProjectID,
|
||||||
|
UserID: userID,
|
||||||
|
RoleKeys: roleKeys,
|
||||||
|
CreationDate: event.CreationDate,
|
||||||
|
}
|
||||||
|
u.fillData(grant, event.ResourceOwner)
|
||||||
|
} else {
|
||||||
|
newRoles := roleKeys
|
||||||
|
if grant.RoleKeys != nil {
|
||||||
|
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||||
|
} else {
|
||||||
|
grant.RoleKeys = newRoles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grant.Sequence = event.Sequence
|
||||||
|
grant.ChangeDate = event.CreationDate
|
||||||
|
return u.view.PutUserGrant(grant, event.Sequence)
|
||||||
|
case org_es_model.OrgMemberRemoved,
|
||||||
|
proj_es_model.ProjectMemberRemoved,
|
||||||
|
proj_es_model.ProjectGrantMemberRemoved:
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func suffixRoles(suffix string, roles []string) []string {
|
||||||
|
suffixedRoles := make([]string, len(roles))
|
||||||
|
for i := 0; i < len(roles); i++ {
|
||||||
|
suffixedRoles[i] = roles[i] + ":" + suffix
|
||||||
|
}
|
||||||
|
return suffixedRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExistingRoles(rolePrefix string, existingRoles, newRoles []string) []string {
|
||||||
|
mergedRoles := make([]string, 0)
|
||||||
|
for _, existing := range existingRoles {
|
||||||
|
if !strings.HasPrefix(existing, rolePrefix) {
|
||||||
|
mergedRoles = append(mergedRoles, existing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(mergedRoles, newRoles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) setIamProjectID() error {
|
||||||
|
if u.iamProjectID != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
iam, err := u.iamEvents.IamByID(context.Background(), u.iamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !iam.SetUpDone {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "HANDL-s5DTs", "Setup not done")
|
||||||
|
}
|
||||||
|
u.iamProjectID = iam.IamProjectID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) fillData(grant *view_model.UserGrantView, resourceOwner string) (err error) {
|
||||||
|
user, err := u.userEvents.UserByID(context.Background(), grant.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.fillUserData(grant, user)
|
||||||
|
project, err := u.projectEvents.ProjectByID(context.Background(), grant.ProjectID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.fillProjectData(grant, project)
|
||||||
|
|
||||||
|
org, err := u.orgEvents.OrgByID(context.TODO(), org_model.NewOrg(resourceOwner))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
u.fillOrgData(grant, org)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) fillUserData(grant *view_model.UserGrantView, user *usr_model.User) {
|
||||||
|
grant.UserName = user.UserName
|
||||||
|
grant.FirstName = user.FirstName
|
||||||
|
grant.LastName = user.LastName
|
||||||
|
grant.Email = user.EmailAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) fillProjectData(grant *view_model.UserGrantView, project *proj_model.Project) {
|
||||||
|
grant.ProjectName = project.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
|
||||||
|
grant.OrgDomain = org.Domain
|
||||||
|
grant.OrgName = org.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-8is4s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||||
|
return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip)
|
||||||
|
}
|
@ -45,10 +45,8 @@ func (u *UserSession) Process(event *models.Event) (err error) {
|
|||||||
switch event.Type {
|
switch event.Type {
|
||||||
case es_model.UserPasswordCheckSucceeded,
|
case es_model.UserPasswordCheckSucceeded,
|
||||||
es_model.UserPasswordCheckFailed,
|
es_model.UserPasswordCheckFailed,
|
||||||
es_model.UserPasswordChanged,
|
|
||||||
es_model.MfaOtpCheckSucceeded,
|
es_model.MfaOtpCheckSucceeded,
|
||||||
es_model.MfaOtpCheckFailed,
|
es_model.MfaOtpCheckFailed:
|
||||||
es_model.MfaOtpRemoved:
|
|
||||||
eventData, err := view_model.UserSessionFromEvent(event)
|
eventData, err := view_model.UserSessionFromEvent(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -66,14 +64,22 @@ func (u *UserSession) Process(event *models.Event) (err error) {
|
|||||||
State: int32(req_model.UserSessionStateActive),
|
State: int32(req_model.UserSessionStateActive),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
session.AppendEvent(event)
|
return u.updateSession(session, event)
|
||||||
|
case es_model.UserPasswordChanged,
|
||||||
|
es_model.MfaOtpRemoved:
|
||||||
|
sessions, err := u.view.UserSessionsByUserID(event.AggregateID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, session := range sessions {
|
||||||
|
if err := u.updateSession(session, event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
return u.view.ProcessedUserSessionSequence(event.Sequence)
|
return u.view.ProcessedUserSessionSequence(event.Sequence)
|
||||||
}
|
}
|
||||||
if err := u.FillUserInfo(session, event.AggregateID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return u.view.PutUserSession(session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserSession) OnError(event *models.Event, err error) error {
|
func (u *UserSession) OnError(event *models.Event, err error) error {
|
||||||
@ -81,7 +87,18 @@ func (u *UserSession) OnError(event *models.Event, err error) error {
|
|||||||
return spooler.HandleError(event, err, u.view.GetLatestUserSessionFailedEvent, u.view.ProcessedUserSessionFailedEvent, u.view.ProcessedUserSessionSequence, u.errorCountUntilSkip)
|
return spooler.HandleError(event, err, u.view.GetLatestUserSessionFailedEvent, u.view.ProcessedUserSessionFailedEvent, u.view.ProcessedUserSessionSequence, u.errorCountUntilSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserSession) FillUserInfo(session *view_model.UserSessionView, id string) error {
|
func (u *UserSession) updateSession(session *view_model.UserSessionView, event *models.Event) error {
|
||||||
|
session.Sequence = event.Sequence
|
||||||
|
session.AppendEvent(event)
|
||||||
|
if session.UserName == "" {
|
||||||
|
if err := u.fillUserInfo(session, event.AggregateID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u.view.PutUserSession(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserSession) fillUserInfo(session *view_model.UserSessionView, id string) error {
|
||||||
user, err := u.userEvents.UserByID(context.Background(), id)
|
user, err := u.userEvents.UserByID(context.Background(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2,6 +2,10 @@ package eventsourcing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||||
|
es_iam "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
es_org "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/eventstore"
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||||
@ -10,18 +14,23 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
||||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
"github.com/caos/zitadel/internal/config/types"
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
es_int "github.com/caos/zitadel/internal/eventstore"
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||||
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
|
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
"github.com/caos/zitadel/internal/id"
|
"github.com/caos/zitadel/internal/id"
|
||||||
|
es_key "github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||||
es_policy "github.com/caos/zitadel/internal/policy/repository/eventsourcing"
|
es_policy "github.com/caos/zitadel/internal/policy/repository/eventsourcing"
|
||||||
|
es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
es_user "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
es_user "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
SearchLimit uint64
|
||||||
Eventstore es_int.Config
|
Eventstore es_int.Config
|
||||||
AuthRequest cache.Config
|
AuthRequest cache.Config
|
||||||
View types.SQL
|
View types.SQL
|
||||||
Spooler spooler.SpoolerConfig
|
Spooler spooler.SpoolerConfig
|
||||||
|
KeyConfig es_key.KeyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type EsRepository struct {
|
type EsRepository struct {
|
||||||
@ -29,9 +38,15 @@ type EsRepository struct {
|
|||||||
eventstore.UserRepo
|
eventstore.UserRepo
|
||||||
eventstore.AuthRequestRepo
|
eventstore.AuthRequestRepo
|
||||||
eventstore.TokenRepo
|
eventstore.TokenRepo
|
||||||
|
eventstore.KeyRepository
|
||||||
|
eventstore.ApplicationRepo
|
||||||
|
eventstore.UserSessionRepo
|
||||||
|
eventstore.UserGrantRepo
|
||||||
|
eventstore.OrgRepository
|
||||||
|
eventstore.IamRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error) {
|
func Start(conf Config, authZ auth.Config, systemDefaults sd.SystemDefaults, authZRepo *authz_repo.EsRepository) (*EsRepository, error) {
|
||||||
es, err := es_int.Start(conf.Eventstore)
|
es, err := es_int.Start(conf.Eventstore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -41,7 +56,14 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
view, err := auth_view.StartView(sqlClient)
|
|
||||||
|
keyAlgorithm, err := crypto.NewAESCrypto(conf.KeyConfig.EncryptionConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idGenerator := id.SonyFlakeGenerator
|
||||||
|
|
||||||
|
view, err := auth_view.StartView(sqlClient, keyAlgorithm, idGenerator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -70,8 +92,35 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
repos := handler.EventstoreRepos{UserEvents: user}
|
key, err := es_key.StartKey(es, conf.KeyConfig, keyAlgorithm, idGenerator)
|
||||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, repos)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err := es_proj.StartProject(
|
||||||
|
es_proj.ProjectConfig{
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
Eventstore: es,
|
||||||
|
},
|
||||||
|
systemDefaults,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iam, err := es_iam.StartIam(
|
||||||
|
es_iam.IamConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
},
|
||||||
|
systemDefaults,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
org := es_org.StartOrg(es_org.OrgConfig{Eventstore: es})
|
||||||
|
|
||||||
|
repos := handler.EventstoreRepos{UserEvents: user, ProjectEvents: project, OrgEvents: org, IamEvents: iam}
|
||||||
|
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, repos, systemDefaults)
|
||||||
|
|
||||||
return &EsRepository{
|
return &EsRepository{
|
||||||
spool,
|
spool,
|
||||||
@ -86,13 +135,41 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
|
|||||||
View: view,
|
View: view,
|
||||||
UserSessionViewProvider: view,
|
UserSessionViewProvider: view,
|
||||||
UserViewProvider: view,
|
UserViewProvider: view,
|
||||||
IdGenerator: id.SonyFlakeGenerator,
|
UserEventProvider: user,
|
||||||
|
IdGenerator: idGenerator,
|
||||||
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
|
||||||
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
|
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
|
||||||
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
|
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
|
||||||
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
|
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
|
||||||
},
|
},
|
||||||
eventstore.TokenRepo{View: view},
|
eventstore.TokenRepo{View: view},
|
||||||
|
eventstore.KeyRepository{
|
||||||
|
KeyEvents: key,
|
||||||
|
View: view,
|
||||||
|
SigningKeyRotation: conf.KeyConfig.SigningKeyRotation.Duration,
|
||||||
|
},
|
||||||
|
eventstore.ApplicationRepo{
|
||||||
|
View: view,
|
||||||
|
ProjectEvents: project,
|
||||||
|
},
|
||||||
|
eventstore.UserSessionRepo{
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
eventstore.UserGrantRepo{
|
||||||
|
SearchLimit: conf.SearchLimit,
|
||||||
|
View: view,
|
||||||
|
IamID: systemDefaults.IamID,
|
||||||
|
Auth: authZ,
|
||||||
|
AuthZRepo: authZRepo,
|
||||||
|
},
|
||||||
|
eventstore.OrgRepository{
|
||||||
|
SearchLimit: conf.SearchLimit,
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
eventstore.IamRepository{
|
||||||
|
IamEvents: iam,
|
||||||
|
IamID: systemDefaults.IamID,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package spooler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||||
@ -17,12 +18,12 @@ type SpoolerConfig struct {
|
|||||||
Handlers handler.Configs
|
Handlers handler.Configs
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, repos handler.EventstoreRepos) *spooler.Spooler {
|
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, repos handler.EventstoreRepos, systemDefaults sd.SystemDefaults) *spooler.Spooler {
|
||||||
spoolerConfig := spooler.Config{
|
spoolerConfig := spooler.Config{
|
||||||
Eventstore: es,
|
Eventstore: es,
|
||||||
Locker: &locker{dbClient: sql},
|
Locker: &locker{dbClient: sql},
|
||||||
ConcurrentTasks: c.ConcurrentTasks,
|
ConcurrentTasks: c.ConcurrentTasks,
|
||||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, repos),
|
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, repos, systemDefaults),
|
||||||
}
|
}
|
||||||
spool := spoolerConfig.New()
|
spool := spoolerConfig.New()
|
||||||
spool.Start()
|
spool.Start()
|
||||||
|
105
internal/auth/repository/eventsourcing/view/application.go
Normal file
105
internal/auth/repository/eventsourcing/view/application.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
global_model "github.com/caos/zitadel/internal/model"
|
||||||
|
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
applicationTable = "auth.applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) ApplicationByID(appID string) (*model.ApplicationView, error) {
|
||||||
|
return view.ApplicationByID(v.Db, applicationTable, appID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
|
||||||
|
return view.SearchApplications(v.Db, applicationTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutApplication(project *model.ApplicationView) error {
|
||||||
|
err := view.PutApplication(v.Db, applicationTable, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedApplicationSequence(project.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedApplicationSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestApplicationSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(applicationTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedApplicationSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(applicationTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(applicationTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedApplicationFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ApplicationByClientID(_ context.Context, clientID string) (*model.ApplicationView, error) {
|
||||||
|
req := &proj_model.ApplicationSearchRequest{
|
||||||
|
Limit: 1,
|
||||||
|
Queries: []*proj_model.ApplicationSearchQuery{
|
||||||
|
{
|
||||||
|
Key: proj_model.APPLICATIONSEARCHKEY_OIDC_CLIENT_ID,
|
||||||
|
Method: global_model.SEARCHMETHOD_EQUALS,
|
||||||
|
Value: clientID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apps, count, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(err, "VIEW-sd6JQ", "cannot find client")
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "VIEW-dfw3as", "cannot find client")
|
||||||
|
}
|
||||||
|
return apps[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string) ([]string, error) {
|
||||||
|
app, err := v.ApplicationByClientID(ctx, clientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req := &proj_model.ApplicationSearchRequest{
|
||||||
|
Queries: []*proj_model.ApplicationSearchQuery{
|
||||||
|
{
|
||||||
|
Key: proj_model.APPLICATIONSEARCHKEY_PROJECT_ID,
|
||||||
|
Method: global_model.SEARCHMETHOD_EQUALS,
|
||||||
|
Value: app.ProjectID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apps, _, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(err, "VIEW-Gd24q", "cannot find applications")
|
||||||
|
}
|
||||||
|
ids := make([]string, 0, len(apps))
|
||||||
|
for _, app := range apps {
|
||||||
|
if !app.IsOIDC {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ids = append(ids, app.OIDCClientID)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
72
internal/auth/repository/eventsourcing/view/key.go
Normal file
72
internal/auth/repository/eventsourcing/view/key.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
key_model "github.com/caos/zitadel/internal/key/model"
|
||||||
|
"github.com/caos/zitadel/internal/key/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/key/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyTable = "auth.keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) KeyByIDAndType(keyID string, private bool) (*model.KeyView, error) {
|
||||||
|
return view.KeyByIDAndType(v.Db, keyTable, keyID, private)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetSigningKey() (*key_model.SigningKey, error) {
|
||||||
|
key, err := view.GetSigningKey(v.Db, keyTable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key_model.SigningKeyFromKeyView(model.KeyViewToModel(key), v.keyAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetActiveKeySet() ([]*key_model.PublicKey, error) {
|
||||||
|
keys, err := view.GetActivePublicKeys(v.Db, keyTable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key_model.PublicKeysFromKeyView(model.KeyViewsToModel(keys), v.keyAlgorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutKeys(privateKey, publicKey *model.KeyView, eventSequence uint64) error {
|
||||||
|
err := view.PutKeys(v.Db, keyTable, privateKey, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedKeySequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteKey(keyID string, private bool, eventSequence uint64) error {
|
||||||
|
err := view.DeleteKey(v.Db, keyTable, keyID, private)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedKeySequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteKeyPair(keyID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteKeyPair(v.Db, keyTable, keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedKeySequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestKeySequence() (uint64, error) {
|
||||||
|
return v.latestSequence(keyTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedKeySequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(keyTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestKeyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(keyTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedKeyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
43
internal/auth/repository/eventsourcing/view/org.go
Normal file
43
internal/auth/repository/eventsourcing/view/org.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/org/model"
|
||||||
|
org_view "github.com/caos/zitadel/internal/org/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
orgTable = "auth.orgs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) OrgByID(orgID string) (*org_view.OrgView, error) {
|
||||||
|
return org_view.OrgByID(v.Db, orgTable, orgID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_view.OrgView, int, error) {
|
||||||
|
return org_view.SearchOrgs(v.Db, orgTable, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutOrg(org *org_view.OrgView) error {
|
||||||
|
err := org_view.PutOrg(v.Db, orgTable, org)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedOrgSequence(org.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(orgTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedOrgFailedEvent(failedEvent *view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestOrgSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(orgTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedOrgSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(orgTable, eventSequence)
|
||||||
|
}
|
@ -20,17 +20,23 @@ func (v *View) IsTokenValid(tokenID string) (bool, error) {
|
|||||||
return view.IsTokenValid(v.Db, tokenTable, tokenID)
|
return view.IsTokenValid(v.Db, tokenTable, tokenID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) CreateToken(agentID, applicationID, userID string, lifetime time.Duration) (*model.Token, error) {
|
func (v *View) CreateToken(agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*model.Token, error) {
|
||||||
|
id, err := v.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
token := &model.Token{
|
token := &model.Token{
|
||||||
|
ID: id,
|
||||||
CreationDate: now,
|
CreationDate: now,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ApplicationID: applicationID,
|
ApplicationID: applicationID,
|
||||||
UserAgentID: agentID,
|
UserAgentID: agentID,
|
||||||
|
Scopes: scopes,
|
||||||
|
Audience: audience,
|
||||||
Expiration: now.Add(lifetime),
|
Expiration: now.Add(lifetime),
|
||||||
}
|
}
|
||||||
err := view.PutToken(v.Db, tokenTable, token)
|
if err := view.PutToken(v.Db, tokenTable, token); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return token, nil
|
return token, nil
|
||||||
|
64
internal/auth/repository/eventsourcing/view/user_grant.go
Normal file
64
internal/auth/repository/eventsourcing/view/user_grant.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userGrantTable = "auth.user_grants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) UserGrantByID(grantID string) (*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantByID(v.Db, userGrantTable, grantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantByIDs(resourceOwnerID, projectID, userID string) (*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantByIDs(v.Db, userGrantTable, resourceOwnerID, projectID, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantsByUserID(userID string) ([]*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantsByUserID(v.Db, userGrantTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
|
||||||
|
return view.SearchUserGrants(v.Db, userGrantTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error {
|
||||||
|
err := view.PutUserGrant(v.Db, userGrantTable, grant)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedUserGrantSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteUserGrant(v.Db, userGrantTable, grantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedUserGrantSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestUserGrantSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(userGrantTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(userGrantTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(userGrantTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedUserGrantFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
@ -10,14 +10,14 @@ const (
|
|||||||
userSessionTable = "auth.user_sessions"
|
userSessionTable = "auth.user_sessions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (v *View) UserSessionByID(sessionID string) (*model.UserSessionView, error) {
|
|
||||||
return view.UserSessionByID(v.Db, userSessionTable, sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) UserSessionByIDs(agentID, userID string) (*model.UserSessionView, error) {
|
func (v *View) UserSessionByIDs(agentID, userID string) (*model.UserSessionView, error) {
|
||||||
return view.UserSessionByIDs(v.Db, userSessionTable, agentID, userID)
|
return view.UserSessionByIDs(v.Db, userSessionTable, agentID, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *View) UserSessionsByUserID(userID string) ([]*model.UserSessionView, error) {
|
||||||
|
return view.UserSessionsByUserID(v.Db, userSessionTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
func (v *View) UserSessionsByAgentID(agentID string) ([]*model.UserSessionView, error) {
|
func (v *View) UserSessionsByAgentID(agentID string) ([]*model.UserSessionView, error) {
|
||||||
return view.UserSessionsByAgentID(v.Db, userSessionTable, agentID)
|
return view.UserSessionsByAgentID(v.Db, userSessionTable, agentID)
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,26 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
type View struct {
|
type View struct {
|
||||||
Db *gorm.DB
|
Db *gorm.DB
|
||||||
|
keyAlgorithm crypto.EncryptionAlgorithm
|
||||||
|
idGenerator id.Generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartView(sqlClient *sql.DB) (*View, error) {
|
func StartView(sqlClient *sql.DB, keyAlgorithm crypto.EncryptionAlgorithm, idGenerator id.Generator) (*View, error) {
|
||||||
gorm, err := gorm.Open("postgres", sqlClient)
|
gorm, err := gorm.Open("postgres", sqlClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &View{
|
return &View{
|
||||||
Db: gorm,
|
Db: gorm,
|
||||||
|
keyAlgorithm: keyAlgorithm,
|
||||||
|
idGenerator: idGenerator,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
11
internal/auth/repository/iam.go
Normal file
11
internal/auth/repository/iam.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/iam/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamRepository interface {
|
||||||
|
GetIam(ctx context.Context) (*model.Iam, error)
|
||||||
|
}
|
14
internal/auth/repository/key.go
Normal file
14
internal/auth/repository/key.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyRepository interface {
|
||||||
|
GenerateSigningKeyPair(ctx context.Context, algorithm string) error
|
||||||
|
GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, timer <-chan time.Time)
|
||||||
|
GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error)
|
||||||
|
}
|
@ -9,4 +9,8 @@ type Repository interface {
|
|||||||
UserRepository
|
UserRepository
|
||||||
AuthRequestRepository
|
AuthRequestRepository
|
||||||
TokenRepository
|
TokenRepository
|
||||||
|
ApplicationRepository
|
||||||
|
KeyRepository
|
||||||
|
UserSessionRepository
|
||||||
|
UserGrantRepository
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TokenRepository interface {
|
type TokenRepository interface {
|
||||||
CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*model.Token, error)
|
CreateToken(ctx context.Context, agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*model.Token, error)
|
||||||
IsTokenValid(ctx context.Context, tokenID string) (bool, error)
|
IsTokenValid(ctx context.Context, tokenID string) (bool, error)
|
||||||
|
TokenByID(ctx context.Context, tokenID string) (*model.Token, error)
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,20 @@ type UserRepository interface {
|
|||||||
|
|
||||||
myUserRepo
|
myUserRepo
|
||||||
SkipMfaInit(ctx context.Context, userID string) error
|
SkipMfaInit(ctx context.Context, userID string) error
|
||||||
|
|
||||||
RequestPasswordReset(ctx context.Context, username string) error
|
RequestPasswordReset(ctx context.Context, username string) error
|
||||||
SetPassword(ctx context.Context, userID, code, password string) error
|
SetPassword(ctx context.Context, userID, code, password string) error
|
||||||
|
ChangePassword(ctx context.Context, userID, old, new string) error
|
||||||
|
|
||||||
|
VerifyEmail(ctx context.Context, userID, code string) error
|
||||||
|
ResendEmailVerificationMail(ctx context.Context, userID string) error
|
||||||
|
|
||||||
|
AddMfaOTP(ctx context.Context, userID string) (*model.OTP, error)
|
||||||
|
VerifyMfaOTPSetup(ctx context.Context, userID, code string) error
|
||||||
|
|
||||||
SignOut(ctx context.Context, agentID, userID string) error
|
SignOut(ctx context.Context, agentID, userID string) error
|
||||||
|
|
||||||
|
UserByID(ctx context.Context, userID string) (*model.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type myUserRepo interface {
|
type myUserRepo interface {
|
||||||
@ -37,6 +47,6 @@ type myUserRepo interface {
|
|||||||
ChangeMyPassword(ctx context.Context, old, new string) error
|
ChangeMyPassword(ctx context.Context, old, new string) error
|
||||||
|
|
||||||
AddMyMfaOTP(ctx context.Context) (*model.OTP, error)
|
AddMyMfaOTP(ctx context.Context) (*model.OTP, error)
|
||||||
VerifyMyMfaOTP(ctx context.Context, code string) error
|
VerifyMyMfaOTPSetup(ctx context.Context, code string) error
|
||||||
RemoveMyMfaOTP(ctx context.Context) error
|
RemoveMyMfaOTP(ctx context.Context) error
|
||||||
}
|
}
|
||||||
|
12
internal/auth/repository/user_grant.go
Normal file
12
internal/auth/repository/user_grant.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepository interface {
|
||||||
|
SearchMyUserGrants(ctx context.Context, request *model.UserGrantSearchRequest) (*model.UserGrantSearchResponse, error)
|
||||||
|
SearchMyProjectOrgs(ctx context.Context, request *model.UserGrantSearchRequest) (*model.ProjectOrgSearchResponse, error)
|
||||||
|
SearchMyZitadelPermissions(ctx context.Context) ([]string, error)
|
||||||
|
}
|
10
internal/auth/repository/user_session.go
Normal file
10
internal/auth/repository/user_session.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/user/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserSessionRepository interface {
|
||||||
|
GetMyUserSessions(ctx context.Context) ([]*model.UserSessionView, error)
|
||||||
|
}
|
@ -2,29 +2,36 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthRequest struct {
|
type AuthRequest struct {
|
||||||
ID string
|
ID string
|
||||||
AgentID string
|
AgentID string
|
||||||
CreationDate time.Time
|
CreationDate time.Time
|
||||||
ChangeDate time.Time
|
ChangeDate time.Time
|
||||||
BrowserInfo *BrowserInfo
|
BrowserInfo *BrowserInfo
|
||||||
ApplicationID string
|
ApplicationID string
|
||||||
CallbackURI string
|
CallbackURI string
|
||||||
TransferState string
|
TransferState string
|
||||||
Prompt Prompt
|
Prompt Prompt
|
||||||
PossibleLOAs []LevelOfAssurance
|
PossibleLOAs []LevelOfAssurance
|
||||||
UiLocales []string
|
UiLocales []string
|
||||||
LoginHint string
|
LoginHint string
|
||||||
PreselectedUserID string
|
MaxAuthAge uint32
|
||||||
MaxAuthAge uint32
|
Request Request
|
||||||
Request Request
|
|
||||||
|
|
||||||
levelOfAssurance LevelOfAssurance
|
levelOfAssurance LevelOfAssurance
|
||||||
projectApplicationIDs []string
|
UserID string
|
||||||
UserID string
|
UserName string
|
||||||
PossibleSteps []NextStep
|
UserOrgID string
|
||||||
|
PossibleSteps []NextStep
|
||||||
|
PasswordVerified bool
|
||||||
|
MfasVerified []MfaType
|
||||||
|
Audience []string
|
||||||
|
AuthTime time.Time
|
||||||
|
Code string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Prompt int32
|
type Prompt int32
|
||||||
@ -46,22 +53,30 @@ const (
|
|||||||
func NewAuthRequest(id, agentID string, info *BrowserInfo, applicationID, callbackURI, transferState string,
|
func NewAuthRequest(id, agentID string, info *BrowserInfo, applicationID, callbackURI, transferState string,
|
||||||
prompt Prompt, possibleLOAs []LevelOfAssurance, uiLocales []string, loginHint, preselectedUserID string, maxAuthAge uint32, request Request) *AuthRequest {
|
prompt Prompt, possibleLOAs []LevelOfAssurance, uiLocales []string, loginHint, preselectedUserID string, maxAuthAge uint32, request Request) *AuthRequest {
|
||||||
return &AuthRequest{
|
return &AuthRequest{
|
||||||
ID: id,
|
ID: id,
|
||||||
AgentID: agentID,
|
AgentID: agentID,
|
||||||
BrowserInfo: info,
|
BrowserInfo: info,
|
||||||
ApplicationID: applicationID,
|
ApplicationID: applicationID,
|
||||||
CallbackURI: callbackURI,
|
CallbackURI: callbackURI,
|
||||||
TransferState: transferState,
|
TransferState: transferState,
|
||||||
Prompt: prompt,
|
Prompt: prompt,
|
||||||
PossibleLOAs: possibleLOAs,
|
PossibleLOAs: possibleLOAs,
|
||||||
UiLocales: uiLocales,
|
UiLocales: uiLocales,
|
||||||
LoginHint: loginHint,
|
LoginHint: loginHint,
|
||||||
PreselectedUserID: preselectedUserID,
|
UserID: preselectedUserID,
|
||||||
MaxAuthAge: maxAuthAge,
|
MaxAuthAge: maxAuthAge,
|
||||||
Request: request,
|
Request: request,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAuthRequestFromType(requestType AuthRequestType) (*AuthRequest, error) {
|
||||||
|
request, ok := authRequestTypeMapping[requestType]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "MODEL-ds2kl", "invalid request type")
|
||||||
|
}
|
||||||
|
return &AuthRequest{Request: request}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *AuthRequest) IsValid() bool {
|
func (a *AuthRequest) IsValid() bool {
|
||||||
return a.ID != "" &&
|
return a.ID != "" &&
|
||||||
a.AgentID != "" &&
|
a.AgentID != "" &&
|
||||||
@ -80,3 +95,9 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
|
|||||||
a.BrowserInfo = info
|
a.BrowserInfo = info
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *AuthRequest) SetUserInfo(userID string, userName string, userOrgID string) {
|
||||||
|
a.UserID = userID
|
||||||
|
a.UserName = userName
|
||||||
|
a.UserOrgID = userOrgID
|
||||||
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api"
|
||||||
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
|
)
|
||||||
|
|
||||||
type BrowserInfo struct {
|
type BrowserInfo struct {
|
||||||
UserAgent string
|
UserAgent string
|
||||||
@ -8,6 +14,14 @@ type BrowserInfo struct {
|
|||||||
RemoteIP net.IP
|
RemoteIP net.IP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BrowserInfoFromRequest(r *http.Request) *BrowserInfo {
|
||||||
|
return &BrowserInfo{
|
||||||
|
UserAgent: r.Header.Get(api.UserAgent),
|
||||||
|
AcceptLanguage: r.Header.Get(api.AcceptLanguage),
|
||||||
|
RemoteIP: http_util.RemoteIPFromRequest(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *BrowserInfo) IsValid() bool {
|
func (i *BrowserInfo) IsValid() bool {
|
||||||
return i.UserAgent != "" &&
|
return i.UserAgent != "" &&
|
||||||
i.AcceptLanguage != "" &&
|
i.AcceptLanguage != "" &&
|
||||||
|
@ -10,6 +10,7 @@ const (
|
|||||||
NextStepUnspecified NextStepType = iota
|
NextStepUnspecified NextStepType = iota
|
||||||
NextStepLogin
|
NextStepLogin
|
||||||
NextStepUserSelection
|
NextStepUserSelection
|
||||||
|
NextStepInitUser
|
||||||
NextStepPassword
|
NextStepPassword
|
||||||
NextStepChangePassword
|
NextStepChangePassword
|
||||||
NextStepInitPassword
|
NextStepInitPassword
|
||||||
@ -26,9 +27,7 @@ const (
|
|||||||
UserSessionStateTerminated
|
UserSessionStateTerminated
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginStep struct {
|
type LoginStep struct{}
|
||||||
NotFound bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LoginStep) Type() NextStepType {
|
func (s *LoginStep) Type() NextStepType {
|
||||||
return NextStepLogin
|
return NextStepLogin
|
||||||
@ -48,30 +47,33 @@ type UserSelection struct {
|
|||||||
UserSessionState UserSessionState
|
UserSessionState UserSessionState
|
||||||
}
|
}
|
||||||
|
|
||||||
type PasswordStep struct {
|
type InitUserStep struct {
|
||||||
FailureCount uint16
|
PasswordSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InitUserStep) Type() NextStepType {
|
||||||
|
return NextStepInitUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type PasswordStep struct{}
|
||||||
|
|
||||||
func (s *PasswordStep) Type() NextStepType {
|
func (s *PasswordStep) Type() NextStepType {
|
||||||
return NextStepPassword
|
return NextStepPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangePasswordStep struct {
|
type ChangePasswordStep struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ChangePasswordStep) Type() NextStepType {
|
func (s *ChangePasswordStep) Type() NextStepType {
|
||||||
return NextStepChangePassword
|
return NextStepChangePassword
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitPasswordStep struct {
|
type InitPasswordStep struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *InitPasswordStep) Type() NextStepType {
|
func (s *InitPasswordStep) Type() NextStepType {
|
||||||
return NextStepInitPassword
|
return NextStepInitPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyEMailStep struct {
|
type VerifyEMailStep struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *VerifyEMailStep) Type() NextStepType {
|
func (s *VerifyEMailStep) Type() NextStepType {
|
||||||
return NextStepVerifyEmail
|
return NextStepVerifyEmail
|
||||||
@ -87,7 +89,6 @@ func (s *MfaPromptStep) Type() NextStepType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MfaVerificationStep struct {
|
type MfaVerificationStep struct {
|
||||||
FailureCount uint16
|
|
||||||
MfaProviders []MfaType
|
MfaProviders []MfaType
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +96,7 @@ func (s *MfaVerificationStep) Type() NextStepType {
|
|||||||
return NextStepMfaVerify
|
return NextStepMfaVerify
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedirectToCallbackStep struct {
|
type RedirectToCallbackStep struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RedirectToCallbackStep) Type() NextStepType {
|
func (s *RedirectToCallbackStep) Type() NextStepType {
|
||||||
return NextStepRedirectToCallback
|
return NextStepRedirectToCallback
|
||||||
@ -111,7 +111,8 @@ const (
|
|||||||
type MfaLevel int
|
type MfaLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MfaLevelSoftware MfaLevel = iota
|
MfaLevelNotSetUp MfaLevel = iota
|
||||||
|
MfaLevelSoftware
|
||||||
MfaLevelHardware
|
MfaLevelHardware
|
||||||
MfaLevelHardwareCertified
|
MfaLevelHardwareCertified
|
||||||
)
|
)
|
||||||
|
@ -7,6 +7,12 @@ type Request interface {
|
|||||||
|
|
||||||
type AuthRequestType int32
|
type AuthRequestType int32
|
||||||
|
|
||||||
|
var (
|
||||||
|
authRequestTypeMapping = map[AuthRequestType]Request{
|
||||||
|
AuthRequestTypeOIDC: &AuthRequestOIDC{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AuthRequestTypeOIDC AuthRequestType = iota
|
AuthRequestTypeOIDC AuthRequestType = iota
|
||||||
AuthRequestTypeSAML
|
AuthRequestTypeSAML
|
||||||
|
47
internal/auth_request/repository/cache/cache.go
vendored
47
internal/auth_request/repository/cache/cache.go
vendored
@ -5,6 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
"github.com/caos/zitadel/internal/config/types"
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
@ -34,34 +35,62 @@ func (c *AuthRequestCache) Health(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *AuthRequestCache) GetAuthRequestByID(_ context.Context, id string) (*model.AuthRequest, error) {
|
func (c *AuthRequestCache) GetAuthRequestByID(_ context.Context, id string) (*model.AuthRequest, error) {
|
||||||
|
return c.getAuthRequest("id", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthRequestCache) GetAuthRequestByCode(_ context.Context, code string) (*model.AuthRequest, error) {
|
||||||
|
return c.getAuthRequest("code", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthRequestCache) SaveAuthRequest(_ context.Context, request *model.AuthRequest) error {
|
||||||
|
return c.saveAuthRequest(request, "INSERT INTO auth.auth_requests (id, request, request_type) VALUES($1, $2, $3)", request.Request.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthRequestCache) UpdateAuthRequest(_ context.Context, request *model.AuthRequest) error {
|
||||||
|
return c.saveAuthRequest(request, "UPDATE auth.auth_requests SET request = $2, code = $3 WHERE id = $1", request.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthRequestCache) DeleteAuthRequest(_ context.Context, id string) error {
|
||||||
|
_, err := c.client.Exec("DELETE FROM auth.auth_requests WHERE id = $1", id)
|
||||||
|
if err != nil {
|
||||||
|
return caos_errs.ThrowInternal(err, "CACHE-dsHw3", "unable to delete auth request")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *AuthRequestCache) getAuthRequest(key, value string) (*model.AuthRequest, error) {
|
||||||
var b []byte
|
var b []byte
|
||||||
err := c.client.QueryRow("SELECT request FROM auth.authrequests WHERE id = ?", id).Scan(&b)
|
var requestType model.AuthRequestType
|
||||||
|
query := fmt.Sprintf("SELECT request, request_type FROM auth.auth_requests WHERE %s = $1", key)
|
||||||
|
err := c.client.QueryRow(query, value).Scan(&b, &requestType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return nil, caos_errs.ThrowNotFound(err, "CACHE-d24aD", "auth request not found")
|
return nil, caos_errs.ThrowNotFound(err, "CACHE-d24aD", "auth request not found")
|
||||||
}
|
}
|
||||||
return nil, caos_errs.ThrowInternal(err, "CACHE-as3kj", "unable to get auth request from database")
|
return nil, caos_errs.ThrowInternal(err, "CACHE-as3kj", "unable to get auth request from database")
|
||||||
}
|
}
|
||||||
request := new(model.AuthRequest)
|
request, err := model.NewAuthRequestFromType(requestType)
|
||||||
err = json.Unmarshal(b, &request)
|
if err == nil {
|
||||||
|
err = json.Unmarshal(b, request)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, caos_errs.ThrowInternal(err, "CACHE-2wshg", "unable to unmarshal auth request")
|
return nil, caos_errs.ThrowInternal(err, "CACHE-2wshg", "unable to unmarshal auth request")
|
||||||
}
|
}
|
||||||
return request, nil
|
return request, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AuthRequestCache) SaveAuthRequest(_ context.Context, request *model.AuthRequest) error {
|
func (c *AuthRequestCache) saveAuthRequest(request *model.AuthRequest, query string, param interface{}) error {
|
||||||
b, err := json.Marshal(request)
|
b, err := json.Marshal(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caos_errs.ThrowInternal(err, "CACHE-32FH9", "unable to marshal auth request")
|
return caos_errs.ThrowInternal(err, "CACHE-os0GH", "unable to marshal auth request")
|
||||||
}
|
}
|
||||||
stmt, err := c.client.Prepare("INSERT INTO auth.authrequests (id, request) VALUES($1, $2)")
|
stmt, err := c.client.Prepare(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caos_errs.ThrowInternal(err, "CACHE-dswfF", "sql prepare failed")
|
return caos_errs.ThrowInternal(err, "CACHE-su3GK", "sql prepare failed")
|
||||||
}
|
}
|
||||||
_, err = stmt.Exec(request.ID, b)
|
_, err = stmt.Exec(request.ID, b, param)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return caos_errs.ThrowInternal(err, "CACHE-sw4af", "unable to save auth request")
|
return caos_errs.ThrowInternal(err, "CACHE-sj8iS", "unable to save auth request")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
//go:generate mockgen -package mock -destination ./mock/repository.mock.go github.com/caos/zitadel/internal/auth_request/repository Repository
|
//go:generate mockgen -package mock -destination ./mock/repository.mock.go github.com/caos/zitadel/internal/auth_request/repository AuthRequestCache
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package mock
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/auth_request/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewMockAuthRequestRepository(ctrl *gomock.Controller) repository.Repository {
|
|
||||||
repo := NewMockRepository(ctrl)
|
|
||||||
return repo
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
// Code generated by MockGen. DO NOT EDIT.
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: github.com/caos/zitadel/internal/auth_request/repository (interfaces: Repository)
|
// Source: github.com/caos/zitadel/internal/auth_request/repository (interfaces: AuthRequestCache)
|
||||||
|
|
||||||
// Package mock is a generated GoMock package.
|
// Package mock is a generated GoMock package.
|
||||||
package mock
|
package mock
|
||||||
@ -11,31 +11,60 @@ import (
|
|||||||
reflect "reflect"
|
reflect "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockRepository is a mock of Repository interface
|
// MockAuthRequestCache is a mock of AuthRequestCache interface
|
||||||
type MockRepository struct {
|
type MockAuthRequestCache struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockRepositoryMockRecorder
|
recorder *MockAuthRequestCacheMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockRepositoryMockRecorder is the mock recorder for MockRepository
|
// MockAuthRequestCacheMockRecorder is the mock recorder for MockAuthRequestCache
|
||||||
type MockRepositoryMockRecorder struct {
|
type MockAuthRequestCacheMockRecorder struct {
|
||||||
mock *MockRepository
|
mock *MockAuthRequestCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockRepository creates a new mock instance
|
// NewMockAuthRequestCache creates a new mock instance
|
||||||
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
|
func NewMockAuthRequestCache(ctrl *gomock.Controller) *MockAuthRequestCache {
|
||||||
mock := &MockRepository{ctrl: ctrl}
|
mock := &MockAuthRequestCache{ctrl: ctrl}
|
||||||
mock.recorder = &MockRepositoryMockRecorder{mock}
|
mock.recorder = &MockAuthRequestCacheMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
|
func (m *MockAuthRequestCache) EXPECT() *MockAuthRequestCacheMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteAuthRequest mocks base method
|
||||||
|
func (m *MockAuthRequestCache) DeleteAuthRequest(arg0 context.Context, arg1 string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeleteAuthRequest", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAuthRequest indicates an expected call of DeleteAuthRequest
|
||||||
|
func (mr *MockAuthRequestCacheMockRecorder) DeleteAuthRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAuthRequest", reflect.TypeOf((*MockAuthRequestCache)(nil).DeleteAuthRequest), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthRequestByCode mocks base method
|
||||||
|
func (m *MockAuthRequestCache) GetAuthRequestByCode(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetAuthRequestByCode", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(*model.AuthRequest)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthRequestByCode indicates an expected call of GetAuthRequestByCode
|
||||||
|
func (mr *MockAuthRequestCacheMockRecorder) GetAuthRequestByCode(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthRequestByCode", reflect.TypeOf((*MockAuthRequestCache)(nil).GetAuthRequestByCode), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// GetAuthRequestByID mocks base method
|
// GetAuthRequestByID mocks base method
|
||||||
func (m *MockRepository) GetAuthRequestByID(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
|
func (m *MockAuthRequestCache) GetAuthRequestByID(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetAuthRequestByID", arg0, arg1)
|
ret := m.ctrl.Call(m, "GetAuthRequestByID", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*model.AuthRequest)
|
ret0, _ := ret[0].(*model.AuthRequest)
|
||||||
@ -44,13 +73,13 @@ func (m *MockRepository) GetAuthRequestByID(arg0 context.Context, arg1 string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthRequestByID indicates an expected call of GetAuthRequestByID
|
// GetAuthRequestByID indicates an expected call of GetAuthRequestByID
|
||||||
func (mr *MockRepositoryMockRecorder) GetAuthRequestByID(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockAuthRequestCacheMockRecorder) GetAuthRequestByID(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthRequestByID", reflect.TypeOf((*MockRepository)(nil).GetAuthRequestByID), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthRequestByID", reflect.TypeOf((*MockAuthRequestCache)(nil).GetAuthRequestByID), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health mocks base method
|
// Health mocks base method
|
||||||
func (m *MockRepository) Health(arg0 context.Context) error {
|
func (m *MockAuthRequestCache) Health(arg0 context.Context) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Health", arg0)
|
ret := m.ctrl.Call(m, "Health", arg0)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
@ -58,22 +87,35 @@ func (m *MockRepository) Health(arg0 context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Health indicates an expected call of Health
|
// Health indicates an expected call of Health
|
||||||
func (mr *MockRepositoryMockRecorder) Health(arg0 interface{}) *gomock.Call {
|
func (mr *MockAuthRequestCacheMockRecorder) Health(arg0 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockRepository)(nil).Health), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Health", reflect.TypeOf((*MockAuthRequestCache)(nil).Health), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveAuthRequest mocks base method
|
// SaveAuthRequest mocks base method
|
||||||
func (m *MockRepository) SaveAuthRequest(arg0 context.Context, arg1 string) (*model.AuthRequest, error) {
|
func (m *MockAuthRequestCache) SaveAuthRequest(arg0 context.Context, arg1 *model.AuthRequest) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SaveAuthRequest", arg0, arg1)
|
ret := m.ctrl.Call(m, "SaveAuthRequest", arg0, arg1)
|
||||||
ret0, _ := ret[0].(*model.AuthRequest)
|
ret0, _ := ret[0].(error)
|
||||||
ret1, _ := ret[1].(error)
|
return ret0
|
||||||
return ret0, ret1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveAuthRequest indicates an expected call of SaveAuthRequest
|
// SaveAuthRequest indicates an expected call of SaveAuthRequest
|
||||||
func (mr *MockRepositoryMockRecorder) SaveAuthRequest(arg0, arg1 interface{}) *gomock.Call {
|
func (mr *MockAuthRequestCacheMockRecorder) SaveAuthRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveAuthRequest", reflect.TypeOf((*MockRepository)(nil).SaveAuthRequest), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveAuthRequest", reflect.TypeOf((*MockAuthRequestCache)(nil).SaveAuthRequest), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAuthRequest mocks base method
|
||||||
|
func (m *MockAuthRequestCache) UpdateAuthRequest(arg0 context.Context, arg1 *model.AuthRequest) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateAuthRequest", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAuthRequest indicates an expected call of UpdateAuthRequest
|
||||||
|
func (mr *MockAuthRequestCacheMockRecorder) UpdateAuthRequest(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAuthRequest", reflect.TypeOf((*MockAuthRequestCache)(nil).UpdateAuthRequest), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,12 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/auth_request/model"
|
"github.com/caos/zitadel/internal/auth_request/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repository interface {
|
type AuthRequestCache interface {
|
||||||
Health(ctx context.Context) error
|
Health(ctx context.Context) error
|
||||||
|
|
||||||
GetAuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
|
GetAuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error)
|
||||||
SaveAuthRequest(ctx context.Context, id string) (*model.AuthRequest, error)
|
GetAuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error)
|
||||||
|
SaveAuthRequest(ctx context.Context, request *model.AuthRequest) error
|
||||||
|
UpdateAuthRequest(ctx context.Context, request *model.AuthRequest) error
|
||||||
|
DeleteAuthRequest(ctx context.Context, id string) error
|
||||||
}
|
}
|
||||||
|
16
internal/authz/authz.go
Normal file
16
internal/authz/authz.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package authz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Repository eventsourcing.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(ctx context.Context, config Config, authZ auth.Config, systemDefaults sd.SystemDefaults) (*eventsourcing.EsRepository, error) {
|
||||||
|
return eventsourcing.Start(config.Repository, authZ, systemDefaults)
|
||||||
|
}
|
20
internal/authz/repository/eventsourcing/eventstore/iam.go
Normal file
20
internal/authz/repository/eventsourcing/eventstore/iam.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/iam/model"
|
||||||
|
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamRepo struct {
|
||||||
|
IamID string
|
||||||
|
IamEvents *iam_event.IamEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IamRepo) Health(ctx context.Context) error {
|
||||||
|
return repo.IamEvents.Health(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *IamRepo) IamByID(ctx context.Context) (*model.Iam, error) {
|
||||||
|
return repo.IamEvents.IamByID(ctx, repo.IamID)
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenVerifierRepo struct {
|
||||||
|
TokenVerificationKey [32]byte
|
||||||
|
IamID string
|
||||||
|
IamEvents *iam_event.IamEventstore
|
||||||
|
ProjectEvents *proj_event.ProjectEventstore
|
||||||
|
View *view.View
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *TokenVerifierRepo) VerifyAccessToken(ctx context.Context, tokenString, appName, appID string) (userID string, clientID string, agentID string, err error) {
|
||||||
|
clientID, err = repo.verifierClientID(ctx, appName, appID)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", caos_errs.ThrowPermissionDenied(nil, "APP-ptTIF2", "invalid token")
|
||||||
|
}
|
||||||
|
//TODO: use real key
|
||||||
|
tokenID, err := crypto.DecryptAESString(tokenString, string(repo.TokenVerificationKey[:32]))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", caos_errs.ThrowPermissionDenied(nil, "APP-8EF0zZ", "invalid token")
|
||||||
|
}
|
||||||
|
token, err := repo.View.TokenByID(tokenID)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", caos_errs.ThrowPermissionDenied(err, "APP-BxUSiL", "invalid token")
|
||||||
|
}
|
||||||
|
if !token.Expiration.After(time.Now().UTC()) {
|
||||||
|
return "", "", "", caos_errs.ThrowPermissionDenied(err, "APP-k9KS0", "invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, aud := range token.Audience {
|
||||||
|
if clientID == aud {
|
||||||
|
return token.UserID, clientID, token.UserAgentID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", "", "", caos_errs.ThrowPermissionDenied(nil, "APP-Zxfako", "invalid audience")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *TokenVerifierRepo) ProjectIDByClientID(ctx context.Context, clientID string) (projectID string, err error) {
|
||||||
|
app, err := repo.View.ApplicationByOIDCClientID(clientID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return app.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *TokenVerifierRepo) verifierClientID(ctx context.Context, appName, appClientID string) (string, error) {
|
||||||
|
if appClientID != "" {
|
||||||
|
return appClientID, nil
|
||||||
|
}
|
||||||
|
iam, err := repo.IamEvents.IamByID(ctx, repo.IamID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
app, err := repo.View.ApplicationByProjecIDAndAppName(iam.IamProjectID, appName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return app.OIDCClientID, nil
|
||||||
|
}
|
102
internal/authz/repository/eventsourcing/eventstore/user_grant.go
Normal file
102
internal/authz/repository/eventsourcing/eventstore/user_grant.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package eventstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepo struct {
|
||||||
|
View *view.View
|
||||||
|
IamID string
|
||||||
|
IamProjectID string
|
||||||
|
Auth auth.Config
|
||||||
|
IamEvents *iam_event.IamEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) Health() error {
|
||||||
|
return repo.View.Health()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) ResolveGrants(ctx context.Context) (*auth.Grant, error) {
|
||||||
|
err := repo.fillIamProjectID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctxData := auth.GetCtxData(ctx)
|
||||||
|
|
||||||
|
orgGrant, err := repo.View.UserGrantByIDs(ctxData.OrgID, repo.IamProjectID, ctxData.UserID)
|
||||||
|
if err != nil && !caos_errs.IsNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
iamAdminGrant, err := repo.View.UserGrantByIDs(repo.IamID, repo.IamProjectID, ctxData.UserID)
|
||||||
|
if err != nil && !caos_errs.IsNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeOrgAndAdminGrant(ctxData, orgGrant, iamAdminGrant), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]string, error) {
|
||||||
|
grant, err := repo.ResolveGrants(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions := &grant_model.Permissions{Permissions: []string{}}
|
||||||
|
for _, role := range grant.Roles {
|
||||||
|
roleName, ctxID := auth.SplitPermission(role)
|
||||||
|
for _, mapping := range repo.Auth.RolePermissionMappings {
|
||||||
|
if mapping.Role == roleName {
|
||||||
|
permissions.AppendPermissions(ctxID, mapping.Permissions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return permissions.Permissions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *UserGrantRepo) fillIamProjectID(ctx context.Context) error {
|
||||||
|
if repo.IamProjectID != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
iam, err := repo.IamEvents.IamByID(ctx, repo.IamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !iam.SetUpDone {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-skiwS", "Setup not done")
|
||||||
|
}
|
||||||
|
repo.IamProjectID = iam.IamProjectID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeOrgAndAdminGrant(ctxData auth.CtxData, orgGrant, iamAdminGrant *model.UserGrantView) (grant *auth.Grant) {
|
||||||
|
if orgGrant != nil {
|
||||||
|
roles := orgGrant.RoleKeys
|
||||||
|
if iamAdminGrant != nil {
|
||||||
|
roles = addIamAdminRoles(roles, iamAdminGrant.RoleKeys)
|
||||||
|
}
|
||||||
|
grant = &auth.Grant{OrgID: orgGrant.ResourceOwner, Roles: roles}
|
||||||
|
} else if iamAdminGrant != nil {
|
||||||
|
grant = &auth.Grant{
|
||||||
|
OrgID: ctxData.OrgID,
|
||||||
|
Roles: iamAdminGrant.RoleKeys,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return grant
|
||||||
|
}
|
||||||
|
|
||||||
|
func addIamAdminRoles(orgRoles, iamAdminRoles []string) []string {
|
||||||
|
result := make([]string, 0)
|
||||||
|
result = append(result, iamAdminRoles...)
|
||||||
|
for _, role := range orgRoles {
|
||||||
|
if !auth.ExistsPerm(result, role) {
|
||||||
|
result = append(result, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||||
|
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
handler
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
applicationTable = "authz.applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Application) MinimumCycleDuration() time.Duration { return p.cycleDuration }
|
||||||
|
|
||||||
|
func (p *Application) ViewModel() string {
|
||||||
|
return applicationTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
sequence, err := p.view.GetLatestApplicationSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return eventsourcing.ProjectQuery(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) Process(event *models.Event) (err error) {
|
||||||
|
app := new(view_model.ApplicationView)
|
||||||
|
switch event.Type {
|
||||||
|
case es_model.ApplicationAdded:
|
||||||
|
app.AppendEvent(event)
|
||||||
|
case es_model.ApplicationChanged,
|
||||||
|
es_model.OIDCConfigAdded,
|
||||||
|
es_model.OIDCConfigChanged,
|
||||||
|
es_model.ApplicationDeactivated,
|
||||||
|
es_model.ApplicationReactivated:
|
||||||
|
err := app.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app, err = p.view.ApplicationByID(app.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
app.AppendEvent(event)
|
||||||
|
case es_model.ApplicationRemoved:
|
||||||
|
err := app.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.view.DeleteApplication(app.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return p.view.ProcessedApplicationSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.view.PutApplication(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Application) OnError(event *models.Event, spoolerError error) error {
|
||||||
|
logging.LogWithFields("SPOOL-sjZw", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||||
|
return spooler.HandleError(event, spoolerError, p.view.GetLatestApplicationFailedEvent, p.view.ProcessedApplicationFailedEvent, p.view.ProcessedApplicationSequence, p.errorCountUntilSkip)
|
||||||
|
}
|
49
internal/authz/repository/eventsourcing/handler/handler.go
Normal file
49
internal/authz/repository/eventsourcing/handler/handler.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
iam_events "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
|
||||||
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Configs map[string]*Config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
MinimumCycleDuration types.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
view *view.View
|
||||||
|
bulkLimit uint64
|
||||||
|
cycleDuration time.Duration
|
||||||
|
errorCountUntilSkip uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventstoreRepos struct {
|
||||||
|
IamEvents *iam_events.IamEventstore
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos, systemDefaults sd.SystemDefaults) []spooler.Handler {
|
||||||
|
return []spooler.Handler{
|
||||||
|
&UserGrant{
|
||||||
|
handler: handler{view, bulkLimit, configs.cycleDuration("UserGrant"), errorCount},
|
||||||
|
eventstore: eventstore,
|
||||||
|
iamID: systemDefaults.IamID,
|
||||||
|
iamEvents: repos.IamEvents,
|
||||||
|
},
|
||||||
|
&Application{handler: handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (configs Configs) cycleDuration(viewModel string) time.Duration {
|
||||||
|
c, ok := configs[viewModel]
|
||||||
|
if !ok {
|
||||||
|
return 1 * time.Second
|
||||||
|
}
|
||||||
|
return c.MinimumCycleDuration.Duration
|
||||||
|
}
|
226
internal/authz/repository/eventsourcing/handler/user_grant.go
Normal file
226
internal/authz/repository/eventsourcing/handler/user_grant.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
iam_events "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
|
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||||
|
proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||||
|
view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
handler
|
||||||
|
eventstore eventstore.Eventstore
|
||||||
|
iamEvents *iam_events.IamEventstore
|
||||||
|
iamID string
|
||||||
|
iamProjectID string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
userGrantTable = "authz.user_grants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u *UserGrant) MinimumCycleDuration() time.Duration { return u.cycleDuration }
|
||||||
|
|
||||||
|
func (u *UserGrant) ViewModel() string {
|
||||||
|
return userGrantTable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) EventQuery() (*models.SearchQuery, error) {
|
||||||
|
if u.iamProjectID == "" {
|
||||||
|
err := u.setIamProjectID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sequence, err := u.view.GetLatestUserGrantSequence()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(iam_es_model.IamAggregate, org_es_model.OrgAggregate, proj_es_model.ProjectAggregate).
|
||||||
|
LatestSequenceFilter(sequence), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) Process(event *models.Event) (err error) {
|
||||||
|
switch event.AggregateType {
|
||||||
|
case proj_es_model.ProjectAggregate:
|
||||||
|
err = u.processProject(event)
|
||||||
|
case iam_es_model.IamAggregate:
|
||||||
|
err = u.processIamMember(event, "IAM", false)
|
||||||
|
case org_es_model.OrgAggregate:
|
||||||
|
return u.processOrg(event)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processProject(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectMemberRemoved:
|
||||||
|
member := new(proj_es_model.ProjectMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "PROJECT", true, member.UserID, member.Roles)
|
||||||
|
case proj_es_model.ProjectGrantMemberAdded, proj_es_model.ProjectGrantMemberChanged, proj_es_model.ProjectGrantMemberRemoved:
|
||||||
|
member := new(proj_es_model.ProjectGrantMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "PROJECT_GRANT", true, member.UserID, member.Roles)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processOrg(event *models.Event) (err error) {
|
||||||
|
switch event.Type {
|
||||||
|
case org_es_model.OrgMemberAdded, org_es_model.OrgMemberChanged, org_es_model.OrgMemberRemoved:
|
||||||
|
member := new(org_es_model.OrgMember)
|
||||||
|
member.SetData(event)
|
||||||
|
return u.processMember(event, "ORG", false, member.UserID, member.Roles)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processIamMember(event *models.Event, rolePrefix string, suffix bool) error {
|
||||||
|
member := new(iam_es_model.IamMember)
|
||||||
|
|
||||||
|
switch event.Type {
|
||||||
|
case iam_es_model.IamMemberAdded, iam_es_model.IamMemberChanged:
|
||||||
|
member.SetData(event)
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
grant = &view_model.UserGrantView{
|
||||||
|
ID: u.iamProjectID + member.UserID,
|
||||||
|
ResourceOwner: u.iamID,
|
||||||
|
OrgName: u.iamID,
|
||||||
|
OrgDomain: u.iamID,
|
||||||
|
ProjectID: u.iamProjectID,
|
||||||
|
UserID: member.UserID,
|
||||||
|
RoleKeys: member.Roles,
|
||||||
|
CreationDate: event.CreationDate,
|
||||||
|
}
|
||||||
|
if suffix {
|
||||||
|
grant.RoleKeys = suffixRoles(event.AggregateID, grant.RoleKeys)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRoles := member.Roles
|
||||||
|
if grant.RoleKeys != nil {
|
||||||
|
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||||
|
} else {
|
||||||
|
grant.RoleKeys = newRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
grant.Sequence = event.Sequence
|
||||||
|
grant.ChangeDate = event.CreationDate
|
||||||
|
return u.view.PutUserGrant(grant, grant.Sequence)
|
||||||
|
case iam_es_model.IamMemberRemoved:
|
||||||
|
member.SetData(event)
|
||||||
|
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) processMember(event *models.Event, rolePrefix string, suffix bool, userID string, roleKeys []string) error {
|
||||||
|
switch event.Type {
|
||||||
|
case org_es_model.OrgMemberAdded, proj_es_model.ProjectMemberAdded, proj_es_model.ProjectGrantMemberAdded,
|
||||||
|
org_es_model.OrgMemberChanged, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectGrantMemberChanged:
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||||
|
if err != nil && !errors.IsNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if suffix {
|
||||||
|
roleKeys = suffixRoles(event.AggregateID, roleKeys)
|
||||||
|
}
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
grant = &view_model.UserGrantView{
|
||||||
|
ID: u.iamProjectID + event.ResourceOwner + userID,
|
||||||
|
ResourceOwner: event.ResourceOwner,
|
||||||
|
ProjectID: u.iamProjectID,
|
||||||
|
UserID: userID,
|
||||||
|
RoleKeys: roleKeys,
|
||||||
|
CreationDate: event.CreationDate,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newRoles := roleKeys
|
||||||
|
if grant.RoleKeys != nil {
|
||||||
|
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||||
|
} else {
|
||||||
|
grant.RoleKeys = newRoles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grant.Sequence = event.Sequence
|
||||||
|
grant.ChangeDate = event.CreationDate
|
||||||
|
return u.view.PutUserGrant(grant, event.Sequence)
|
||||||
|
case org_es_model.OrgMemberRemoved,
|
||||||
|
proj_es_model.ProjectMemberRemoved,
|
||||||
|
proj_es_model.ProjectGrantMemberRemoved:
|
||||||
|
|
||||||
|
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||||
|
default:
|
||||||
|
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func suffixRoles(suffix string, roles []string) []string {
|
||||||
|
suffixedRoles := make([]string, len(roles))
|
||||||
|
for i := 0; i < len(roles); i++ {
|
||||||
|
suffixedRoles[i] = roles[i] + ":" + suffix
|
||||||
|
}
|
||||||
|
return suffixedRoles
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExistingRoles(rolePrefix string, existingRoles, newRoles []string) []string {
|
||||||
|
mergedRoles := make([]string, 0)
|
||||||
|
for _, existing := range existingRoles {
|
||||||
|
if !strings.HasPrefix(existing, rolePrefix) {
|
||||||
|
mergedRoles = append(mergedRoles, existing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(mergedRoles, newRoles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) setIamProjectID() error {
|
||||||
|
if u.iamProjectID != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
iam, err := u.iamEvents.IamByID(context.Background(), u.iamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !iam.SetUpDone {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "HANDL-s5DTs", "Setup not done")
|
||||||
|
}
|
||||||
|
u.iamProjectID = iam.IamProjectID
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserGrant) OnError(event *models.Event, err error) error {
|
||||||
|
logging.LogWithFields("SPOOL-8is4s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||||
|
return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip)
|
||||||
|
}
|
98
internal/authz/repository/eventsourcing/repository.go
Normal file
98
internal/authz/repository/eventsourcing/repository.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/handler"
|
||||||
|
es_iam "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
|
es_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/auth_request/repository/cache"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/spooler"
|
||||||
|
authz_view "github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||||
|
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
es_key "github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Eventstore es_int.Config
|
||||||
|
AuthRequest cache.Config
|
||||||
|
View types.SQL
|
||||||
|
Spooler spooler.SpoolerConfig
|
||||||
|
KeyConfig es_key.KeyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type EsRepository struct {
|
||||||
|
spooler *es_spol.Spooler
|
||||||
|
eventstore.UserGrantRepo
|
||||||
|
eventstore.IamRepo
|
||||||
|
eventstore.TokenVerifierRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(conf Config, authZ auth.Config, systemDefaults sd.SystemDefaults) (*EsRepository, error) {
|
||||||
|
es, err := es_int.Start(conf.Eventstore)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlClient, err := conf.View.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idGenerator := id.SonyFlakeGenerator
|
||||||
|
view, err := authz_view.StartView(sqlClient, idGenerator)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iam, err := es_iam.StartIam(es_iam.IamConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
}, systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
project, err := es_proj.StartProject(es_proj.ProjectConfig{
|
||||||
|
Eventstore: es,
|
||||||
|
Cache: conf.Eventstore.Cache,
|
||||||
|
}, systemDefaults)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repos := handler.EventstoreRepos{IamEvents: iam}
|
||||||
|
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, repos, systemDefaults)
|
||||||
|
|
||||||
|
return &EsRepository{
|
||||||
|
spool,
|
||||||
|
eventstore.UserGrantRepo{
|
||||||
|
View: view,
|
||||||
|
IamID: systemDefaults.IamID,
|
||||||
|
Auth: authZ,
|
||||||
|
IamEvents: iam,
|
||||||
|
},
|
||||||
|
eventstore.IamRepo{
|
||||||
|
IamID: systemDefaults.IamID,
|
||||||
|
IamEvents: iam,
|
||||||
|
},
|
||||||
|
eventstore.TokenVerifierRepo{
|
||||||
|
//TODO: Add Token Verification Key
|
||||||
|
IamID: systemDefaults.IamID,
|
||||||
|
IamEvents: iam,
|
||||||
|
ProjectEvents: project,
|
||||||
|
View: view,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *EsRepository) Health(ctx context.Context) error {
|
||||||
|
if err := repo.UserGrantRepo.Health(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
19
internal/authz/repository/eventsourcing/spooler/lock.go
Normal file
19
internal/authz/repository/eventsourcing/spooler/lock.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package spooler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
es_locker "github.com/caos/zitadel/internal/eventstore/locker"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lockTable = "authz.locks"
|
||||||
|
)
|
||||||
|
|
||||||
|
type locker struct {
|
||||||
|
dbClient *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *locker) Renew(lockerID, viewModel string, waitTime time.Duration) error {
|
||||||
|
return es_locker.Renew(l.dbClient, lockTable, lockerID, viewModel, waitTime)
|
||||||
|
}
|
127
internal/authz/repository/eventsourcing/spooler/lock_test.go
Normal file
127
internal/authz/repository/eventsourcing/spooler/lock_test.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
package spooler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbMock struct {
|
||||||
|
db *sql.DB
|
||||||
|
mock sqlmock.Sqlmock
|
||||||
|
}
|
||||||
|
|
||||||
|
func mockDB(t *testing.T) *dbMock {
|
||||||
|
mockDB := dbMock{}
|
||||||
|
var err error
|
||||||
|
mockDB.db, mockDB.mock, err = sqlmock.New()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error occured while creating stub db %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mockDB.mock.MatchExpectationsInOrder(true)
|
||||||
|
|
||||||
|
return &mockDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectCommit() *dbMock {
|
||||||
|
db.mock.ExpectCommit()
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectRollback() *dbMock {
|
||||||
|
db.mock.ExpectRollback()
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectBegin() *dbMock {
|
||||||
|
db.mock.ExpectBegin()
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectSavepoint() *dbMock {
|
||||||
|
db.mock.ExpectExec("SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectReleaseSavepoint() *dbMock {
|
||||||
|
db.mock.ExpectExec("RELEASE SAVEPOINT").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *dbMock) expectRenew(lockerID, view string, affectedRows int64) *dbMock {
|
||||||
|
query := db.mock.
|
||||||
|
ExpectExec(`INSERT INTO authz\.locks \(object_type, locker_id, locked_until\) VALUES \(\$1, \$2, now\(\)\+\$3\) ON CONFLICT \(object_type\) DO UPDATE SET locked_until = now\(\)\+\$4, locker_id = \$5 WHERE \(locks\.locked_until < now\(\) OR locks\.locker_id = \$6\) AND locks\.object_type = \$7`).
|
||||||
|
WithArgs(view, lockerID, sqlmock.AnyArg(), sqlmock.AnyArg(), lockerID, lockerID, view).
|
||||||
|
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||||
|
|
||||||
|
if affectedRows == 0 {
|
||||||
|
query.WillReturnResult(sqlmock.NewResult(0, 0))
|
||||||
|
} else {
|
||||||
|
query.WillReturnResult(sqlmock.NewResult(1, affectedRows))
|
||||||
|
}
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_locker_Renew(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
db *dbMock
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
lockerID string
|
||||||
|
viewModel string
|
||||||
|
waitTime time.Duration
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "renew succeeded",
|
||||||
|
fields: fields{
|
||||||
|
db: mockDB(t).
|
||||||
|
expectBegin().
|
||||||
|
expectSavepoint().
|
||||||
|
expectRenew("locker", "view", 1).
|
||||||
|
expectReleaseSavepoint().
|
||||||
|
expectCommit(),
|
||||||
|
},
|
||||||
|
args: args{lockerID: "locker", viewModel: "view", waitTime: 1 * time.Second},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "renew now rows updated",
|
||||||
|
fields: fields{
|
||||||
|
db: mockDB(t).
|
||||||
|
expectBegin().
|
||||||
|
expectSavepoint().
|
||||||
|
expectRenew("locker", "view", 0).
|
||||||
|
expectRollback(),
|
||||||
|
},
|
||||||
|
args: args{lockerID: "locker", viewModel: "view", waitTime: 1 * time.Second},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
l := &locker{
|
||||||
|
dbClient: tt.fields.db.db,
|
||||||
|
}
|
||||||
|
if err := l.Renew(tt.args.lockerID, tt.args.viewModel, tt.args.waitTime); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("locker.Renew() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if err := tt.fields.db.mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Errorf("not all database expectations met: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
31
internal/authz/repository/eventsourcing/spooler/spooler.go
Normal file
31
internal/authz/repository/eventsourcing/spooler/spooler.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package spooler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/handler"
|
||||||
|
"github.com/caos/zitadel/internal/authz/repository/eventsourcing/view"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
|
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SpoolerConfig struct {
|
||||||
|
BulkLimit uint64
|
||||||
|
FailureCountUntilSkip uint64
|
||||||
|
ConcurrentTasks int
|
||||||
|
Handlers handler.Configs
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, sql *sql.DB, repos handler.EventstoreRepos, systemDefaults sd.SystemDefaults) *spooler.Spooler {
|
||||||
|
spoolerConfig := spooler.Config{
|
||||||
|
Eventstore: es,
|
||||||
|
Locker: &locker{dbClient: sql},
|
||||||
|
ConcurrentTasks: c.ConcurrentTasks,
|
||||||
|
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, repos, systemDefaults),
|
||||||
|
}
|
||||||
|
spool := spoolerConfig.New()
|
||||||
|
spool.Start()
|
||||||
|
return spool
|
||||||
|
}
|
60
internal/authz/repository/eventsourcing/view/application.go
Normal file
60
internal/authz/repository/eventsourcing/view/application.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
applicationTable = "authz.applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) ApplicationByID(appID string) (*model.ApplicationView, error) {
|
||||||
|
return view.ApplicationByID(v.Db, applicationTable, appID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ApplicationByOIDCClientID(clientID string) (*model.ApplicationView, error) {
|
||||||
|
return view.ApplicationByOIDCClientID(v.Db, applicationTable, clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ApplicationByProjecIDAndAppName(projectID, appName string) (*model.ApplicationView, error) {
|
||||||
|
return view.ApplicationByProjectIDAndAppName(v.Db, applicationTable, projectID, appName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
|
||||||
|
return view.SearchApplications(v.Db, applicationTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutApplication(project *model.ApplicationView) error {
|
||||||
|
err := view.PutApplication(v.Db, applicationTable, project)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedApplicationSequence(project.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedApplicationSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestApplicationSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(applicationTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedApplicationSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(applicationTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(applicationTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedApplicationFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
17
internal/authz/repository/eventsourcing/view/error_event.go
Normal file
17
internal/authz/repository/eventsourcing/view/error_event.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
errTable = "authz.failed_event"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) saveFailedEvent(failedEvent *view.FailedEvent) error {
|
||||||
|
return view.SaveFailedEvent(v.Db, errTable, failedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) latestFailedEvent(viewName string, sequence uint64) (*view.FailedEvent, error) {
|
||||||
|
return view.LatestFailedEvent(v.Db, errTable, viewName, sequence)
|
||||||
|
}
|
17
internal/authz/repository/eventsourcing/view/sequence.go
Normal file
17
internal/authz/repository/eventsourcing/view/sequence.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sequencesTable = "authz.current_sequences"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) saveCurrentSequence(viewName string, sequence uint64) error {
|
||||||
|
return view.SaveCurrentSequence(v.Db, sequencesTable, viewName, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) latestSequence(viewName string) (uint64, error) {
|
||||||
|
return view.LatestSequence(v.Db, sequencesTable, viewName)
|
||||||
|
}
|
59
internal/authz/repository/eventsourcing/view/token.go
Normal file
59
internal/authz/repository/eventsourcing/view/token.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/token/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/token/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenTable = "auth.tokens"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) TokenByID(tokenID string) (*model.Token, error) {
|
||||||
|
return view.TokenByID(v.Db, tokenTable, tokenID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) IsTokenValid(tokenID string) (bool, error) {
|
||||||
|
return view.IsTokenValid(v.Db, tokenTable, tokenID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutToken(token *model.Token) error {
|
||||||
|
err := view.PutToken(v.Db, tokenTable, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedTokenSequence(token.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteToken(tokenID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteToken(v.Db, tokenTable, tokenID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedTokenSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteSessionTokens(agentID, userID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteTokens(v.Db, tokenTable, agentID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedTokenSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestTokenSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(tokenTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedTokenSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(tokenTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestTokenFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(tokenTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedTokenFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
64
internal/authz/repository/eventsourcing/view/user_grant.go
Normal file
64
internal/authz/repository/eventsourcing/view/user_grant.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view"
|
||||||
|
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||||
|
global_view "github.com/caos/zitadel/internal/view"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
userGrantTable = "authz.user_grants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v *View) UserGrantByID(grantID string) (*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantByID(v.Db, userGrantTable, grantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantByIDs(resourceOwnerID, projectID, userID string) (*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantByIDs(v.Db, userGrantTable, resourceOwnerID, projectID, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantsByUserID(userID string) ([]*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantsByUserID(v.Db, userGrantTable, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView, error) {
|
||||||
|
return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
|
||||||
|
return view.SearchUserGrants(v.Db, userGrantTable, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error {
|
||||||
|
err := view.PutUserGrant(v.Db, userGrantTable, grant)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return v.ProcessedUserGrantSequence(sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error {
|
||||||
|
err := view.DeleteUserGrant(v.Db, userGrantTable, grantID)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.ProcessedUserGrantSequence(eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestUserGrantSequence() (uint64, error) {
|
||||||
|
return v.latestSequence(userGrantTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error {
|
||||||
|
return v.saveCurrentSequence(userGrantTable, eventSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||||
|
return v.latestFailedEvent(userGrantTable, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) ProcessedUserGrantFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||||
|
return v.saveFailedEvent(failedEvent)
|
||||||
|
}
|
28
internal/authz/repository/eventsourcing/view/view.go
Normal file
28
internal/authz/repository/eventsourcing/view/view.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package view
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
|
|
||||||
|
"github.com/jinzhu/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type View struct {
|
||||||
|
Db *gorm.DB
|
||||||
|
idGenerator id.Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartView(sqlClient *sql.DB, idGenerator id.Generator) (*View, error) {
|
||||||
|
gorm, err := gorm.Open("postgres", sqlClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &View{
|
||||||
|
Db: gorm,
|
||||||
|
idGenerator: idGenerator,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *View) Health() (err error) {
|
||||||
|
return v.Db.DB().Ping()
|
||||||
|
}
|
11
internal/authz/repository/iam.go
Normal file
11
internal/authz/repository/iam.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/iam/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IamRepository interface {
|
||||||
|
Health(ctx context.Context) error
|
||||||
|
IamByID(ctx context.Context, id string) (*model.Iam, error)
|
||||||
|
}
|
11
internal/authz/repository/repository.go
Normal file
11
internal/authz/repository/repository.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
Health(context.Context) error
|
||||||
|
UserGrantRepository
|
||||||
|
IamRepository
|
||||||
|
}
|
10
internal/authz/repository/token_verifier.go
Normal file
10
internal/authz/repository/token_verifier.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TokenVerifierRepository interface {
|
||||||
|
VerifyAccessToken(ctx context.Context, appName string) (string, string, string, error)
|
||||||
|
ProjectIDByClientID(ctx context.Context, clientID string) (string, error)
|
||||||
|
}
|
11
internal/authz/repository/user_grant.go
Normal file
11
internal/authz/repository/user_grant.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/caos/zitadel/internal/api/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrantRepository interface {
|
||||||
|
ResolveGrants(ctx context.Context) (*auth.Grant, error)
|
||||||
|
SearchMyZitadelPermissions(ctx context.Context) ([]string, error)
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
package crypto
|
package crypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,6 +38,23 @@ type CryptoValue struct {
|
|||||||
Crypted []byte
|
Crypted []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CryptoValue) Value() (driver.Value, error) {
|
||||||
|
if c == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return json.Marshal(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CryptoValue) Scan(src interface{}) error {
|
||||||
|
if b, ok := src.([]byte); ok {
|
||||||
|
return json.Unmarshal(b, c)
|
||||||
|
}
|
||||||
|
if s, ok := src.(string); ok {
|
||||||
|
return json.Unmarshal([]byte(s), c)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type CryptoType int
|
type CryptoType int
|
||||||
|
|
||||||
func Crypt(value []byte, c Crypto) (*CryptoValue, error) {
|
func Crypt(value []byte, c Crypto) (*CryptoValue, error) {
|
||||||
|
103
internal/crypto/rsa.go
Normal file
103
internal/crypto/rsa.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package crypto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error) {
|
||||||
|
privkey, err := rsa.GenerateKey(rand.Reader, bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return privkey, &privkey.PublicKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateEncryptedKeyPair(bits int, alg EncryptionAlgorithm) (*CryptoValue, *CryptoValue, error) {
|
||||||
|
privateKey, publicKey, err := GenerateKeyPair(bits)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return EncryptKeys(privateKey, publicKey, alg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrivateKeyToBytes(priv *rsa.PrivateKey) []byte {
|
||||||
|
return pem.EncodeToMemory(
|
||||||
|
&pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeyToBytes(pub *rsa.PublicKey) ([]byte, error) {
|
||||||
|
pubASN1, err := x509.MarshalPKIXPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "RSA PUBLIC KEY",
|
||||||
|
Bytes: pubASN1,
|
||||||
|
})
|
||||||
|
|
||||||
|
return pubBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode(priv)
|
||||||
|
enc := x509.IsEncryptedPEMBlock(block)
|
||||||
|
b := block.Bytes
|
||||||
|
var err error
|
||||||
|
if enc {
|
||||||
|
b, err = x509.DecryptPEMBlock(block, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key, err := x509.ParsePKCS1PrivateKey(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BytesToPublicKey(pub []byte) (*rsa.PublicKey, error) {
|
||||||
|
block, _ := pem.Decode(pub)
|
||||||
|
enc := x509.IsEncryptedPEMBlock(block)
|
||||||
|
b := block.Bytes
|
||||||
|
var err error
|
||||||
|
if enc {
|
||||||
|
b, err = x509.DecryptPEMBlock(block, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ifc, err := x509.ParsePKIXPublicKey(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, ok := ifc.(*rsa.PublicKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncryptKeys(privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, alg EncryptionAlgorithm) (*CryptoValue, *CryptoValue, error) {
|
||||||
|
encryptedPrivateKey, err := Encrypt(PrivateKeyToBytes(privateKey), alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pubKey, err := PublicKeyToBytes(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
encryptedPublicKey, err := Encrypt(pubKey, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return encryptedPrivateKey, encryptedPublicKey, nil
|
||||||
|
}
|
27
internal/form/parser.go
Normal file
27
internal/form/parser.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Parser struct {
|
||||||
|
decoder *schema.Decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewParser() *Parser {
|
||||||
|
d := schema.NewDecoder()
|
||||||
|
d.IgnoreUnknownKeys(true)
|
||||||
|
return &Parser{d}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) Parse(r *http.Request, data interface{}) error {
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return errors.ThrowInternal(err, "FORM-lCC9zI", "error parsing http form")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.decoder.Decode(data, r.Form)
|
||||||
|
}
|
128
internal/i18n/i18n.go
Normal file
128
internal/i18n/i18n.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package i18n
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
i18nPath = "/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Translator struct {
|
||||||
|
bundle *i18n.Bundle
|
||||||
|
cookieName string
|
||||||
|
cookieHandler *http_util.CookieHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type TranslatorConfig struct {
|
||||||
|
DefaultLanguage language.Tag
|
||||||
|
CookieName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTranslator(dir http.FileSystem, config TranslatorConfig) (*Translator, error) {
|
||||||
|
t := new(Translator)
|
||||||
|
var err error
|
||||||
|
t.bundle, err = newBundle(dir, config.DefaultLanguage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t.cookieHandler = http_util.NewCookieHandler()
|
||||||
|
t.cookieName = config.CookieName
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBundle(dir http.FileSystem, defaultLanguage language.Tag) (*i18n.Bundle, error) {
|
||||||
|
bundle := i18n.NewBundle(defaultLanguage)
|
||||||
|
bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
|
||||||
|
bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
|
||||||
|
i18nDir, err := dir.Open(i18nPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowNotFound(err, "I18N-MnXRie", "path not found")
|
||||||
|
}
|
||||||
|
defer i18nDir.Close()
|
||||||
|
files, err := i18nDir.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowNotFound(err, "I18N-Gew23", "cannot read dir")
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if err := addFileToBundle(dir, bundle, file); err != nil {
|
||||||
|
return nil, errors.ThrowNotFound(err, "I18N-ZS2AW", "cannot append file to bundle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bundle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFileToBundle(dir http.FileSystem, bundle *i18n.Bundle, file os.FileInfo) error {
|
||||||
|
f, err := dir.Open("/i18n/" + file.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
content, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bundle.MustParseMessageFileBytes(content, file.Name())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) LocalizeFromRequest(r *http.Request, id string, args map[string]interface{}) string {
|
||||||
|
s, err := t.localizerFromRequest(r).Localize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: id,
|
||||||
|
TemplateData: args,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logging.Log("I18N-MsF5sx").WithError(err).Warnf("missing translation")
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) Localize(id string, args map[string]interface{}) string {
|
||||||
|
s, _ := t.localizer().Localize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: id,
|
||||||
|
TemplateData: args,
|
||||||
|
})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) Lang(r *http.Request) language.Tag {
|
||||||
|
matcher := language.NewMatcher(t.bundle.LanguageTags())
|
||||||
|
tag, _ := language.MatchStrings(matcher, t.langsFromRequest(r)...)
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) {
|
||||||
|
t.cookieHandler.SetCookie(w, t.cookieName, lang.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
|
||||||
|
return t.localizer(t.langsFromRequest(r)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) localizer(langs ...string) *i18n.Localizer {
|
||||||
|
return i18n.NewLocalizer(t.bundle, langs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Translator) langsFromRequest(r *http.Request) []string {
|
||||||
|
langs := make([]string, 0)
|
||||||
|
if r != nil {
|
||||||
|
lang, err := t.cookieHandler.GetCookieValue(r, t.cookieName)
|
||||||
|
if err == nil {
|
||||||
|
langs = append(langs, lang)
|
||||||
|
}
|
||||||
|
langs = append(langs, r.Header.Get("Accept-Language"))
|
||||||
|
}
|
||||||
|
return langs
|
||||||
|
}
|
@ -65,7 +65,7 @@ func (i *Iam) AppendEvent(event *es_models.Event) (err error) {
|
|||||||
i.SetUpDone = true
|
i.SetUpDone = true
|
||||||
case IamProjectSet,
|
case IamProjectSet,
|
||||||
GlobalOrgSet:
|
GlobalOrgSet:
|
||||||
err = i.setData(event)
|
err = i.SetData(event)
|
||||||
case IamMemberAdded:
|
case IamMemberAdded:
|
||||||
err = i.appendAddMemberEvent(event)
|
err = i.appendAddMemberEvent(event)
|
||||||
case IamMemberChanged:
|
case IamMemberChanged:
|
||||||
@ -76,7 +76,7 @@ func (i *Iam) AppendEvent(event *es_models.Event) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iam) setData(event *es_models.Event) error {
|
func (i *Iam) SetData(event *es_models.Event) error {
|
||||||
i.ObjectRoot.AppendEvent(event)
|
i.ObjectRoot.AppendEvent(event)
|
||||||
if err := json.Unmarshal(event.Data, i); err != nil {
|
if err := json.Unmarshal(event.Data, i); err != nil {
|
||||||
logging.Log("EVEN-9sie4").WithError(err).Error("could not unmarshal event data")
|
logging.Log("EVEN-9sie4").WithError(err).Error("could not unmarshal event data")
|
||||||
|
@ -56,7 +56,7 @@ func IamMemberToModel(member *IamMember) *model.IamMember {
|
|||||||
|
|
||||||
func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
|
func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
|
||||||
member := &IamMember{}
|
member := &IamMember{}
|
||||||
err := member.setData(event)
|
err := member.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
|
|||||||
|
|
||||||
func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
|
func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
|
||||||
member := &IamMember{}
|
member := &IamMember{}
|
||||||
err := member.setData(event)
|
err := member.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
|
|||||||
|
|
||||||
func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
|
func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
|
||||||
member := &IamMember{}
|
member := &IamMember{}
|
||||||
err := member.setData(event)
|
err := member.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *IamMember) setData(event *es_models.Event) error {
|
func (m *IamMember) SetData(event *es_models.Event) error {
|
||||||
m.ObjectRoot.AppendEvent(event)
|
m.ObjectRoot.AppendEvent(event)
|
||||||
if err := json.Unmarshal(event.Data, m); err != nil {
|
if err := json.Unmarshal(event.Data, m); err != nil {
|
||||||
logging.Log("EVEN-e4dkp").WithError(err).Error("could not unmarshal event data")
|
logging.Log("EVEN-e4dkp").WithError(err).Error("could not unmarshal event data")
|
||||||
|
46
internal/key/model/key.go
Normal file
46
internal/key/model/key.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyPair struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Usage KeyUsage
|
||||||
|
Algorithm string
|
||||||
|
PrivateKey *Key
|
||||||
|
PublicKey *Key
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyUsage int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyUsageSigning KeyUsage = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u KeyUsage) String() string {
|
||||||
|
switch u {
|
||||||
|
case KeyUsageSigning:
|
||||||
|
return "sig"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Key *crypto.CryptoValue
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyPair) IsValid() bool {
|
||||||
|
return k.Algorithm != "" &&
|
||||||
|
k.PrivateKey != nil && k.PrivateKey.IsValid() &&
|
||||||
|
k.PublicKey != nil && k.PublicKey.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Key) IsValid() bool {
|
||||||
|
return k.Key != nil
|
||||||
|
}
|
120
internal/key/model/key_view.go
Normal file
120
internal/key/model/key_view.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
"github.com/caos/zitadel/internal/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyView struct {
|
||||||
|
ID string
|
||||||
|
Private bool
|
||||||
|
Expiry time.Time
|
||||||
|
Algorithm string
|
||||||
|
Usage KeyUsage
|
||||||
|
Key *crypto.CryptoValue
|
||||||
|
Sequence uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type SigningKey struct {
|
||||||
|
ID string
|
||||||
|
Algorithm string
|
||||||
|
Key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublicKey struct {
|
||||||
|
ID string
|
||||||
|
Algorithm string
|
||||||
|
Usage KeyUsage
|
||||||
|
Key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySearchRequest struct {
|
||||||
|
Offset uint64
|
||||||
|
Limit uint64
|
||||||
|
SortingColumn KeySearchKey
|
||||||
|
Asc bool
|
||||||
|
Queries []*KeySearchQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySearchKey int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
KEYSEARCHKEY_UNSPECIFIED KeySearchKey = iota
|
||||||
|
KEYSEARCHKEY_ID
|
||||||
|
KEYSEARCHKEY_PRIVATE
|
||||||
|
KEYSEARCHKEY_EXPIRY
|
||||||
|
KEYSEARCHKEY_USAGE
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeySearchQuery struct {
|
||||||
|
Key KeySearchKey
|
||||||
|
Method model.SearchMethod
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeySearchResponse struct {
|
||||||
|
Offset uint64
|
||||||
|
Limit uint64
|
||||||
|
TotalResult uint64
|
||||||
|
Result []*KeyView
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *KeySearchRequest) EnsureLimit(limit uint64) {
|
||||||
|
if r.Limit == 0 || r.Limit > limit {
|
||||||
|
r.Limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SigningKeyFromKeyView(key *KeyView, alg crypto.EncryptionAlgorithm) (*SigningKey, error) {
|
||||||
|
if key.Usage != KeyUsageSigning || !key.Private {
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "MODEL-5HBdh", "key must be private signing key")
|
||||||
|
}
|
||||||
|
keyData, err := crypto.Decrypt(key.Key, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKey, err := crypto.BytesToPrivateKey(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &SigningKey{
|
||||||
|
ID: key.ID,
|
||||||
|
Algorithm: key.Algorithm,
|
||||||
|
Key: privateKey,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PublicKeysFromKeyView(keys []*KeyView, alg crypto.EncryptionAlgorithm) ([]*PublicKey, error) {
|
||||||
|
converted := make([]*PublicKey, len(keys))
|
||||||
|
var err error
|
||||||
|
for i, key := range keys {
|
||||||
|
converted[i], err = PublicKeyFromKeyView(key, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return converted, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
func PublicKeyFromKeyView(key *KeyView, alg crypto.EncryptionAlgorithm) (*PublicKey, error) {
|
||||||
|
if key.Private {
|
||||||
|
return nil, errors.ThrowInvalidArgument(nil, "MODEL-dTZa2", "key must be public")
|
||||||
|
}
|
||||||
|
keyData, err := crypto.Decrypt(key.Key, alg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
publicKey, err := crypto.BytesToPublicKey(keyData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &PublicKey{
|
||||||
|
ID: key.ID,
|
||||||
|
Algorithm: key.Algorithm,
|
||||||
|
Usage: key.Usage,
|
||||||
|
Key: publicKey,
|
||||||
|
}, nil
|
||||||
|
}
|
84
internal/key/repository/eventsourcing/eventstore.go
Normal file
84
internal/key/repository/eventsourcing/eventstore.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/config/types"
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
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"
|
||||||
|
"github.com/caos/zitadel/internal/id"
|
||||||
|
key_model "github.com/caos/zitadel/internal/key/model"
|
||||||
|
"github.com/caos/zitadel/internal/key/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyEventstore struct {
|
||||||
|
es_int.Eventstore
|
||||||
|
keySize int
|
||||||
|
keyAlgorithm crypto.EncryptionAlgorithm
|
||||||
|
privateKeyLifetime time.Duration
|
||||||
|
publicKeyLifetime time.Duration
|
||||||
|
idGenerator id.Generator
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyConfig struct {
|
||||||
|
Size int
|
||||||
|
PrivateKeyLifetime types.Duration
|
||||||
|
PublicKeyLifetime types.Duration
|
||||||
|
EncryptionConfig *crypto.KeyConfig
|
||||||
|
SigningKeyRotation types.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartKey(eventstore es_int.Eventstore, config KeyConfig, keyAlgorithm crypto.EncryptionAlgorithm, generator id.Generator) (*KeyEventstore, error) {
|
||||||
|
return &KeyEventstore{
|
||||||
|
Eventstore: eventstore,
|
||||||
|
keySize: config.Size,
|
||||||
|
keyAlgorithm: keyAlgorithm,
|
||||||
|
privateKeyLifetime: config.PrivateKeyLifetime.Duration,
|
||||||
|
publicKeyLifetime: config.PublicKeyLifetime.Duration,
|
||||||
|
idGenerator: generator,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (es *KeyEventstore) GenerateKeyPair(ctx context.Context, usage key_model.KeyUsage, algorithm string) (*key_model.KeyPair, error) {
|
||||||
|
privateKey, publicKey, err := crypto.GenerateEncryptedKeyPair(es.keySize, es.keyAlgorithm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKeyExp := time.Now().UTC().Add(es.privateKeyLifetime)
|
||||||
|
publicKeyExp := time.Now().UTC().Add(es.publicKeyLifetime)
|
||||||
|
return es.CreateKeyPair(ctx, &key_model.KeyPair{
|
||||||
|
ObjectRoot: models.ObjectRoot{},
|
||||||
|
Usage: usage,
|
||||||
|
Algorithm: algorithm,
|
||||||
|
PrivateKey: &key_model.Key{
|
||||||
|
Key: privateKey,
|
||||||
|
Expiry: privateKeyExp,
|
||||||
|
},
|
||||||
|
PublicKey: &key_model.Key{
|
||||||
|
Key: publicKey,
|
||||||
|
Expiry: publicKeyExp,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (es *KeyEventstore) CreateKeyPair(ctx context.Context, pair *key_model.KeyPair) (*key_model.KeyPair, error) {
|
||||||
|
if !pair.IsValid() {
|
||||||
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-G34ga", "Name is required")
|
||||||
|
}
|
||||||
|
id, err := es.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pair.AggregateID = id
|
||||||
|
repoKey := model.KeyPairFromModel(pair)
|
||||||
|
|
||||||
|
createAggregate := KeyPairCreateAggregate(es.AggregateCreator(), repoKey)
|
||||||
|
err = es_sdk.Push(ctx, es.PushAggregates, repoKey.AppendEvents, createAggregate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return model.KeyPairToModel(repoKey), nil
|
||||||
|
}
|
33
internal/key/repository/eventsourcing/key.go
Normal file
33
internal/key/repository/eventsourcing/key.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package eventsourcing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/key/repository/eventsourcing/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func KeyPairQuery(latestSequence uint64) *es_models.SearchQuery {
|
||||||
|
return es_models.NewSearchQuery().
|
||||||
|
AggregateTypeFilter(model.KeyPairAggregate).
|
||||||
|
LatestSequenceFilter(latestSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyPairAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, pair *model.KeyPair) (*es_models.Aggregate, error) {
|
||||||
|
if pair == nil {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-d5HNJA", "existing key pair must not be nil")
|
||||||
|
}
|
||||||
|
return aggCreator.NewAggregate(ctx, pair.AggregateID, model.KeyPairAggregate, model.KeyPairVersion, pair.Sequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyPairCreateAggregate(aggCreator *es_models.AggregateCreator, pair *model.KeyPair) func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
return func(ctx context.Context) (*es_models.Aggregate, error) {
|
||||||
|
agg, err := KeyPairAggregate(ctx, aggCreator, pair)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg.AppendEvent(model.KeyPairAdded, pair)
|
||||||
|
}
|
||||||
|
}
|
90
internal/key/repository/eventsourcing/model/key.go
Normal file
90
internal/key/repository/eventsourcing/model/key.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/caos/logging"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
"github.com/caos/zitadel/internal/key/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyPairVersion = "v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyPair struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
|
||||||
|
Usage int32 `json:"usage"`
|
||||||
|
Algorithm string `json:"algorithm"`
|
||||||
|
PrivateKey *Key `json:"privateKey"`
|
||||||
|
PublicKey *Key `json:"publicKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Key *crypto.CryptoValue `json:"key"`
|
||||||
|
Expiry time.Time `json:"expiry"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyPairFromModel(pair *model.KeyPair) *KeyPair {
|
||||||
|
return &KeyPair{
|
||||||
|
ObjectRoot: pair.ObjectRoot,
|
||||||
|
Usage: int32(pair.Usage),
|
||||||
|
Algorithm: pair.Algorithm,
|
||||||
|
PrivateKey: KeyFromModel(pair.PrivateKey),
|
||||||
|
PublicKey: KeyFromModel(pair.PublicKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyPairToModel(pair *KeyPair) *model.KeyPair {
|
||||||
|
return &model.KeyPair{
|
||||||
|
ObjectRoot: pair.ObjectRoot,
|
||||||
|
Usage: model.KeyUsage(pair.Usage),
|
||||||
|
Algorithm: pair.Algorithm,
|
||||||
|
PrivateKey: KeyToModel(pair.PrivateKey),
|
||||||
|
PublicKey: KeyToModel(pair.PublicKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyFromModel(key *model.Key) *Key {
|
||||||
|
return &Key{
|
||||||
|
Key: key.Key,
|
||||||
|
Expiry: key.Expiry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func KeyToModel(key *Key) *model.Key {
|
||||||
|
return &model.Key{
|
||||||
|
Key: key.Key,
|
||||||
|
Expiry: key.Expiry,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyPair) AppendEvents(events ...*es_models.Event) error {
|
||||||
|
for _, event := range events {
|
||||||
|
if err := k.AppendEvent(event); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyPair) AppendEvent(event *es_models.Event) error {
|
||||||
|
k.ObjectRoot.AppendEvent(event)
|
||||||
|
switch event.Type {
|
||||||
|
case KeyPairAdded:
|
||||||
|
return k.AppendAddKeyPair(event)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KeyPair) AppendAddKeyPair(event *es_models.Event) error {
|
||||||
|
if err := json.Unmarshal(event.Data, k); err != nil {
|
||||||
|
logging.Log("EVEN-Je92s").WithError(err).Error("could not unmarshal event data")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
9
internal/key/repository/eventsourcing/model/types.go
Normal file
9
internal/key/repository/eventsourcing/model/types.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "github.com/caos/zitadel/internal/eventstore/models"
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyPairAggregate models.AggregateType = "key_pair"
|
||||||
|
|
||||||
|
KeyPairAdded models.EventType = "key_pair.added"
|
||||||
|
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user