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:
Livio Amstutz 2020-06-05 07:50:04 +02:00 committed by GitHub
parent 46b60a6968
commit 8a5badddf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
293 changed files with 14189 additions and 3176 deletions

View File

@ -101,6 +101,8 @@ jobs:
- run: go get github.com/rakyll/statik
- run: ./build/console/generate-static.sh
- 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
- uses: actions/upload-artifact@v1
with:

2
.gitignore vendored
View File

@ -32,4 +32,4 @@ cockroach-data/*
#binaries
cmd/zitadel/zitadel
zitadel
**/statik/statik.go

View File

@ -2,4 +2,4 @@
set -eux
go generate pkg/console/console.go
go generate pkg/console/statik/generate.go

5
build/login/generate-static.sh Executable file
View File

@ -0,0 +1,5 @@
#! /bin/sh
set -eux
go generate internal/login/statik/generate.go

View File

@ -1,4 +1,4 @@
AuthZ:
InternalAuthZ:
RolePermissionMappings:
- Role: 'IAM_OWNER'
Permissions:

26
cmd/zitadel/caos_local.sh Normal file → Executable file
View File

@ -1,7 +1,9 @@
BASEDIR=$(dirname "$0")
gopass sync --store zitadel-secrets
# 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 ZITADEL_TRACING_PROJECT_ID=caos-citadel-test
@ -15,24 +17,32 @@ export ZITADEL_EVENTSTORE_HOST=localhost
export ZITADEL_EVENTSTORE_PORT=26257
# 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_USER_VERIFICATION_KEY=UserVerificationKey_1
export ZITADEL_OTP_VERIFICATION_KEY=OTPVerificationKey_1
export ZITADEL_OIDC_KEYS_ID=OIDCKey_1
export ZITADEL_COOKIE_KEY=CookieKey_1
# Notifications
export DEBUG_MODE=TRUE
export TWILIO_SERVICE_SID=$(gopass citadel-secrets/citadel/developer/default/twilio-sid)
export TWILIO_TOKEN=$(gopass citadel-secrets/citadel/developer/default/twilio-auth-token)
export TWILIO_SERVICE_SID=$(gopass zitadel-secrets/zitadel/dev/twilio-sid)
export TWILIO_TOKEN=$(gopass zitadel-secrets/zitadel/dev/twilio-auth-token)
export TWILIO_SENDER_NAME=CAOS AG
export SMTP_HOST=smtp.gmail.com:465
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_NAME=CAOS AG
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
export ZITADEL_ACCOUNTS=http://localhost:61121
#OIDC
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

View File

@ -3,18 +3,20 @@ package main
import (
"context"
"flag"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/authz"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/login"
"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/notification"
tracing "github.com/caos/zitadel/internal/tracing/config"
"github.com/caos/zitadel/pkg/admin"
"github.com/caos/zitadel/pkg/auth"
"github.com/caos/zitadel/pkg/console"
"github.com/caos/zitadel/pkg/login"
"github.com/caos/zitadel/pkg/management"
)
@ -22,13 +24,14 @@ type Config struct {
Mgmt management.Config
Auth auth.Config
Login login.Config
AuthZ authz.Config
Admin admin.Config
Console console.Config
Notification notification.Config
Log logging.Config
Tracing tracing.TracingConfig
AuthZ authz.Config
InternalAuthZ internal_authz.Config
SystemDefaults sd.SystemDefaults
}
@ -48,18 +51,25 @@ func main() {
logging.Log("MAIN-FaF2r").OnError(err).Fatal("cannot read config")
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 {
admin.Start(ctx, conf.Admin, conf.AuthZ, conf.SystemDefaults)
admin.Start(ctx, conf.Admin, authZRepo, conf.InternalAuthZ, conf.SystemDefaults)
}
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 {
auth.Start(ctx, conf.Auth, conf.AuthZ, conf.SystemDefaults)
auth.Start(ctx, conf.Auth, authZRepo, conf.InternalAuthZ, conf.SystemDefaults, authRepo)
}
if *loginEnabled {
err = login.Start(ctx, conf.Login)
logging.Log("MAIN-53RF2").OnError(err).Fatal("error starting login ui")
login.Start(ctx, conf.Login, conf.SystemDefaults, authRepo)
}
if *notificationEnabled {
notification.Start(ctx, conf.Notification, conf.SystemDefaults)

View File

@ -50,6 +50,37 @@ Auth:
GatewayPort: 50021
CustomHeaders:
- 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:
SearchLimit: 100
Eventstore:
@ -66,6 +97,7 @@ Auth:
Config:
MaxCacheSizeInByte: 10485760 #10mb
AuthRequest:
Connection:
Host: $ZITADEL_EVENTSTORE_HOST
Port: $ZITADEL_EVENTSTORE_PORT
User: 'auth'
@ -81,9 +113,48 @@ Auth:
ConcurrentTasks: 4
BulkLimit: 100
FailureCountUntilSkip: 5
KeyConfig:
Size: 2048
PrivateKeyLifetime: 6h
PublicKeyLifetime: 30h
EncryptionConfig:
EncryptionKeyID: $ZITADEL_OIDC_KEYS_ID
SigningKeyRotation: 10s
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:
API:
@ -120,7 +191,6 @@ Admin:
Console:
Port: 50050
StaticDir: /app/console/dist
Notification:

View File

@ -76,7 +76,7 @@ SystemDefaults:
LastName: 'Administrator'
UserName: 'zitadel-global-org-admin@caos.ch'
Email: 'zitadel-global-org-admin@caos.ch'
Password: 'Password'
Password: 'Password1!'
Owners:
- 'zitadel-global-org-admin@caos.ch'
- Name: 'CAOS AG'
@ -86,7 +86,7 @@ SystemDefaults:
LastName: 'Administrator'
UserName: 'zitadel-admin@caos.ch'
Email: 'zitadel-admin@caos.ch'
Password: 'Password'
Password: 'Password1!'
Owners:
- 'zitadel-admin@caos.ch'
Projects:
@ -97,9 +97,9 @@ SystemDefaults:
- Name: 'Admin-API'
- Name: 'Zitadel Console'
RedirectUris:
- '$CITADEL_CONSOLE/auth/callback'
- '$ZITADEL_CONSOLE/auth/callback'
PostLogoutRedirectUris:
- '$CITADEL_CONSOLE/signedout'
- '$ZITADEL_CONSOLE/signedout'
ResponseTypes:
- 'CODE'
GrantTypes:
@ -133,9 +133,9 @@ SystemDefaults:
From: $TWILIO_SENDER_NAME
TemplateData:
InitCode:
Title: 'Zitadel - User Initialisieren'
PreHeader: 'User Initialisieren'
Subject: 'User Initialisieren'
Title: 'Zitadel - User initialisieren'
PreHeader: 'User initialisieren'
Subject: 'User initialisieren'
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.'
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.'
ButtonText: 'Passwort zurücksetzen'
VerifyEmail:
Title: 'Zitadel - Email Verifizieren'
Title: 'Zitadel - Email verifizieren'
PreHeader: 'Email verifizieren'
Subject: 'Email verifizieren'
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.'
ButtonText: 'Passwort zurücksetzen'
ButtonText: 'Email verifizieren'
VerifyPhone:
Title: 'Zitadel - Telefonnummer Verifizieren'
PreHeader: 'Telefonnummer Verifizieren'
Subject: 'Telefonnummer Verifizieren'
Title: 'Zitadel - Telefonnummer verifizieren'
PreHeader: 'Telefonnummer verifizieren'
Subject: 'Telefonnummer verifizieren'
Greeting: 'Hallo {{.FirstName}} {{.LastName}},'
Text: 'Eine Telefonnummer wurde hinzugefügt. Bitte verifiziere diese in dem du folgenden Code eingibst: {{.Code}}'
ButtonText: 'Telefon verifizieren'

12
go.mod
View File

@ -11,16 +11,20 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
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/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/envoyproxy/protoc-gen-validate v0.1.0
github.com/ghodss/yaml v1.0.0
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/mock v1.4.3
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/securecookie v1.1.1
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
@ -41,7 +45,6 @@ require (
github.com/pquerna/otp v1.2.0
github.com/rakyll/statik v0.1.7
github.com/rs/cors v1.7.0
github.com/sirupsen/logrus v1.6.0 // indirect
github.com/sony/sonyflake v1.0.0
github.com/stretchr/testify v1.5.1
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
@ -49,12 +52,13 @@ require (
go.opencensus.io v0.22.3
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
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/tools v0.0.0-20200512001501-aaeff5de670a
google.golang.org/api v0.24.0 // indirect
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380
google.golang.org/grpc v1.29.1
google.golang.org/protobuf v1.22.0
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v2 v2.2.8 // indirect
)

30
go.sum
View File

@ -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/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
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 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc=
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/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
github.com/caos/logging v0.0.1 h1:YSGtO2/+5OWdwilBCou50akoDHAT/OhkbrolkVlR6U0=
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.1/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
github.com/caos/logging v0.0.2/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/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.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.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/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=
@ -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.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
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/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
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-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-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
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-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-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-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=
@ -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-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-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-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
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/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-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
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.3.0/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -3,23 +3,34 @@ package auth
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
)
const (
adminName = "Admin-API"
)
type TokenVerifier struct {
adminID string
authZRepo *authz_repo.EsRepository
}
func Start() (v *TokenVerifier) {
return new(TokenVerifier)
func Start(authZRepo *authz_repo.EsRepository) (v *TokenVerifier) {
return &TokenVerifier{authZRepo: authZRepo}
}
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) {
return nil, nil
func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*auth.Grant, error) {
return v.authZRepo.ResolveGrants(ctx)
}
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
return v.authZRepo.ProjectIDByClientID(ctx, clientID)
}

View File

@ -3,13 +3,12 @@ package eventstore
import (
admin_model "github.com/caos/zitadel/internal/admin/model"
es_models "github.com/caos/zitadel/internal/eventstore/models"
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/eventsourcing/model"
usr_es "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
)
type Setup struct {
*org_es.Org
*model.Org
*usr_es.User
}
@ -17,7 +16,7 @@ func (s *Setup) AppendEvents(events ...*es_models.Event) error {
for _, event := range events {
var err error
switch event.AggregateType {
case org_model.OrgAggregate:
case model.OrgAggregate:
err = s.Org.AppendEvent(event)
case usr_es.UserAggregate:
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 {
return &admin_model.SetupOrg{
Org: org_es.OrgToModel(setup.Org),
Org: model.OrgToModel(setup.Org),
User: usr_es.UserToModel(setup.User),
}
}

View File

@ -1,12 +1,12 @@
package handler
import (
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"time"
"github.com/caos/logging"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"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/view"
)
@ -37,9 +37,9 @@ func (o *Org) Process(event *es_models.Event) error {
org := new(view.OrgView)
switch event.Type {
case org_model.OrgAdded:
case model.OrgAdded:
org.AppendEvent(event)
case org_model.OrgChanged:
case model.OrgChanged:
err := org.SetData(event)
if err != nil {
return err

View File

@ -125,7 +125,7 @@ func (s *Setup) Execute(ctx context.Context) error {
err = setUp.setIamProject(ctx)
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
}

View File

@ -11,9 +11,9 @@ import (
type key int
var (
permissionsKey key
dataKey key
const (
permissionsKey key = 1
dataKey key = 2
)
type CtxData struct {
@ -36,7 +36,7 @@ type Grant struct {
type TokenVerifier interface {
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)
}

View File

@ -11,21 +11,20 @@ func getUserMethodPermissions(ctx context.Context, t TokenVerifier, requiredPerm
if ctxData.IsZero() {
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 {
return nil, nil, err
}
permissions := mapGrantsToPermissions(requiredPerm, grants, authConfig)
permissions := mapGrantToPermissions(requiredPerm, grant, authConfig)
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)
for _, grant := range grants {
for _, role := range grant.Roles {
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
}
}
return resolvedPermissions
}
@ -36,7 +35,7 @@ func mapRoleToPerm(requiredPerm, actualRole string, authConfig *Config, resolved
for _, p := range perms {
if p == requiredPerm {
p = addRoleContextIDToPerm(p, roleContextID)
if !existsPerm(resolvedPermissions, p) {
if !ExistsPerm(resolvedPermissions, p) {
resolvedPermissions = append(resolvedPermissions, p)
}
}
@ -51,7 +50,7 @@ func addRoleContextIDToPerm(perm, roleContextID string) string {
return perm
}
func existsPerm(existing []string, perm string) bool {
func ExistsPerm(existing []string, perm string) bool {
for _, e := range existing {
if e == perm {
return true

View File

@ -12,15 +12,15 @@ func getTestCtx(userID, orgID string) context.Context {
}
type testVerifier struct {
grants []*Grant
grant *Grant
}
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
return "userID", "clientID", "agentID", nil
}
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {
return v.grants, nil
func (v *testVerifier) ResolveGrant(ctx context.Context) (*Grant, error) {
return v.grant, nil
}
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
@ -57,8 +57,8 @@ func Test_GetUserMethodPermissions(t *testing.T) {
name: "Empty Context",
args: args{
ctx: getTestCtx("", ""),
verifier: &testVerifier{grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}}},
verifier: &testVerifier{grant: &Grant{
Roles: []string{"ORG_OWNER"}}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
@ -81,7 +81,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
name: "No Grants",
args: args{
ctx: getTestCtx("", ""),
verifier: &testVerifier{grants: []*Grant{}},
verifier: &testVerifier{grant: &Grant{}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
@ -102,8 +102,8 @@ func Test_GetUserMethodPermissions(t *testing.T) {
name: "Get Permissions",
args: args{
ctx: getTestCtx("userID", "orgID"),
verifier: &testVerifier{grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}}},
verifier: &testVerifier{grant: &Grant{
Roles: []string{"ORG_OWNER"}}},
requiredPerm: "project.read",
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
@ -143,7 +143,7 @@ func Test_GetUserMethodPermissions(t *testing.T) {
func Test_MapGrantsToPermissions(t *testing.T) {
type args struct {
requiredPerm string
grants []*Grant
grant *Grant
authConfig *Config
}
tests := []struct {
@ -155,8 +155,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
name: "One Role existing perm",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}},
grant: &Grant{Roles: []string{"ORG_OWNER"}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
@ -176,8 +175,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
name: "One Role not existing perm",
args: args{
requiredPerm: "project.write",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER"}}},
grant: &Grant{Roles: []string{"ORG_OWNER"}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
@ -197,8 +195,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
name: "Multiple Roles one existing",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER", "IAM_OWNER"}}},
grant: &Grant{Roles: []string{"ORG_OWNER", "IAM_OWNER"}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
@ -218,8 +215,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
name: "Multiple Roles, global and specific",
args: args{
requiredPerm: "project.read",
grants: []*Grant{&Grant{
Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}}},
grant: &Grant{Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}},
authConfig: &Config{
RolePermissionMappings: []RoleMapping{
RoleMapping{
@ -238,7 +234,7 @@ func Test_MapGrantsToPermissions(t *testing.T) {
}
for _, tt := range tests {
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) {
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 {
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 {
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
}

View File

@ -7,6 +7,8 @@ const (
ContentType = "content-type"
Location = "location"
Origin = "origin"
UserAgent = "user-agent"
ForwardedFor = "x-forwarded-for"
ZitadelOrgID = "x-zitadel-orgid"
//TODO: Remove as soon an authentification is implemented

View 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
}

View 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
}

View File

@ -3,23 +3,34 @@ package auth
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
)
const (
authName = "Auth-API"
)
type TokenVerifier struct {
authID string
authZRepo *authz_repo.EsRepository
}
func Start() (v *TokenVerifier) {
return new(TokenVerifier)
func Start(authZRepo *authz_repo.EsRepository) (v *TokenVerifier) {
return &TokenVerifier{authZRepo: authZRepo}
}
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) {
return nil, nil
func (v *TokenVerifier) ResolveGrant(ctx context.Context) (*auth.Grant, error) {
return v.authZRepo.ResolveGrants(ctx)
}
func (v *TokenVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
return "", nil
return v.authZRepo.ProjectIDByClientID(ctx, clientID)
}

View File

@ -1,4 +0,0 @@
package auth
type Config struct {
}

View 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
}

View File

@ -9,7 +9,12 @@ import (
type AuthRequestRepository interface {
CreateAuthRequest(ctx context.Context, request *model.AuthRequest) (*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
SelectUser(ctx context.Context, id, userID string) 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
}

View File

@ -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)
}

View File

@ -4,23 +4,28 @@ import (
"context"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"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"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id"
user_model "github.com/caos/zitadel/internal/user/model"
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"
)
type AuthRequestRepo struct {
UserEvents *user_event.UserEventstore
AuthRequests *cache.AuthRequestCache
AuthRequests cache.AuthRequestCache
View *view.View
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserEventProvider userEventProvider
IdGenerator id.Generator
@ -38,6 +43,10 @@ type userViewProvider interface {
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 {
if err := repo.UserEvents.Health(ctx); err != nil {
return err
@ -51,6 +60,11 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return nil, err
}
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)
if err != nil {
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) {
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)
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 {
return nil, err
}
steps, err := repo.nextSteps(request)
steps, err := repo.nextSteps(ctx, request, true)
if err != nil {
return nil, err
}
@ -71,6 +102,10 @@ func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*m
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 {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
@ -80,8 +115,21 @@ func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username str
if err != nil {
return err
}
request.UserID = user.ID
return repo.AuthRequests.SaveAuthRequest(ctx, request)
request.SetUserInfo(user.ID, user.UserName, user.ResourceOwner)
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 {
@ -89,7 +137,7 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, pas
if err != nil {
return err
}
if request.UserID == userID {
if request.UserID != userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id")
}
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))
}
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 {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
}
steps := make([]model.NextStep, 0)
if request.UserID == "" {
if request.Prompt != model.PromptNone {
steps = append(steps, &model.LoginStep{})
if !checkLoggedIn && request.Prompt == model.PromptNone {
return append(steps, &model.RedirectToCallbackStep{}), nil
}
if request.UserID == "" {
steps = append(steps, &model.LoginStep{})
if request.Prompt == model.PromptSelectAccount {
users, err := repo.usersForUserSelection(request)
if err != nil {
@ -124,15 +186,18 @@ func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.Next
}
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 {
return nil, err
}
user, err := userByID(repo.UserViewProvider, request.UserID)
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
if err != nil {
return nil, err
}
if user.InitRequired {
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
}
if !user.PasswordSet {
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) {
return append(steps, &model.PasswordStep{}), nil
}
request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
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) {
mfaLevel := request.MfaLevel()
required := user.MfaMaxSetUp < mfaLevel
if required || !repo.mfaSkippedOrSetUp(user) {
promptRequired := user.MfaMaxSetUp < mfaLevel
if promptRequired || !repo.mfaSkippedOrSetUp(user) {
return &model.MfaPromptStep{
Required: required,
Required: promptRequired,
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
}, false
}
switch mfaLevel {
default:
fallthrough
case model.MfaLevelNotSetUp:
if user.MfaMaxSetUp == model.MfaLevelNotSetUp {
return nil, true
}
fallthrough
case model.MfaLevelSoftware:
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaSoftwareVerificationType)
request.AuthTime = userSession.MfaSoftwareVerification
return nil, true
}
fallthrough
case model.MfaLevelHardware:
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaHardwareVerificationType)
request.AuthTime = userSession.MfaHardwareVerification
return nil, true
}
}
@ -204,7 +280,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
}
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
if user.MfaMaxSetUp >= 0 {
if user.MfaMaxSetUp > model.MfaLevelNotSetUp {
return true
}
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
@ -222,18 +298,55 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string)
return view_model.UserSessionsToModel(session), nil
}
func userSessionByIDs(provider userSessionViewProvider, agentID, userID string) (*user_model.UserSessionView, error) {
session, err := provider.UserSessionByIDs(agentID, userID)
func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eventProvider userEventProvider, agentID string, user *user_model.UserView) (*user_model.UserSessionView, error) {
session, err := provider.UserSessionByIDs(agentID, user.ID)
if err != nil {
if !errors.IsNotFound(err) {
return nil, err
}
session = &view_model.UserSessionView{}
}
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) {
user, err := provider.UserByID(userID)
func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) {
user, err := viewProvider.UserByID(userID)
if err != nil {
return nil, err
}
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
}

View File

@ -1,6 +1,8 @@
package eventstore
import (
"context"
"encoding/json"
"testing"
"time"
@ -10,8 +12,10 @@ import (
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
"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_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"
)
@ -25,6 +29,16 @@ func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.Use
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 {
PasswordVerification 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")
}
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 {
InitRequired bool
PasswordSet bool
PasswordChangeRequired bool
IsEmailVerified bool
@ -71,6 +104,7 @@ type mockViewUser struct {
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
return &view_model.UserView{
InitRequired: m.InitRequired,
PasswordSet: m.PasswordSet,
PasswordChangeRequired: m.PasswordChangeRequired,
IsEmailVerified: m.IsEmailVerified,
@ -87,6 +121,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
View *view.View
userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider
userEventProvider userEventProvider
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
@ -94,6 +129,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
}
type args struct {
request *model.AuthRequest
checkLoggedIn bool
}
tests := []struct {
name string
@ -105,22 +141,22 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
{
"request nil, error",
fields{},
args{nil},
args{nil, false},
nil,
errors.IsErrorInvalidArgument,
},
{
"user not set, login step",
"prompt none and checkLoggedIn false, callback step",
fields{},
args{&model.AuthRequest{}},
[]model.NextStep{&model.LoginStep{}},
args{&model.AuthRequest{Prompt: model.PromptNone}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
{
"user not set and prompt none, no step",
"user not set, login step",
fields{},
args{&model.AuthRequest{Prompt: model.PromptNone}},
[]model.NextStep{},
args{&model.AuthRequest{}, false},
[]model.NextStep{&model.LoginStep{}},
nil,
},
{
@ -128,7 +164,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false},
nil,
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.LoginStep{},
&model.SelectUserStep{
@ -166,31 +203,63 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
},
{
"usersession not found, not found error",
"user not not found, not found error",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
userViewProvider: &mockViewNoUser{},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
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{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewNoUser{},
userViewProvider: &mockViewUser{
InitRequired: true,
PasswordSet: true,
},
args{&model.AuthRequest{UserID: "UserID"}},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitUserStep{
PasswordSet: true,
}},
nil,
errors.IsNotFound,
},
{
"password not set, init password step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitPasswordStep{}},
nil,
},
@ -201,9 +270,10 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
@ -218,10 +288,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFASTATE_READY),
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
}},
@ -238,11 +309,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
PasswordChangeRequired: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.ChangePasswordStep{}},
nil,
},
@ -255,11 +328,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userViewProvider: &mockViewUser{
PasswordSet: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.VerifyEMailStep{}},
nil,
},
@ -273,11 +348,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
nil,
},
@ -291,11 +368,33 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
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"}},
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{}},
nil,
},
@ -308,12 +407,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
View: tt.fields.View,
UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
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)) {
t.Errorf("nextSteps() wrong error = %v", err)
return
@ -360,14 +460,31 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: -1,
MfaMaxSetUp: model.MfaLevelNotSetUp,
},
},
&model.MfaPromptStep{
MfaProviders: []model.MfaType{},
MfaProviders: []model.MfaType{
model.MfaTypeOTP,
},
},
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",
fields{
@ -376,6 +493,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: model.MfaLevelSoftware,
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
@ -391,6 +509,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: model.MfaLevelSoftware,
OTPState: user_model.MFASTATE_READY,
},
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)
})
}
}

View 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)
}

View 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})
}

View 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
}

View File

@ -13,8 +13,8 @@ type TokenRepo struct {
View *view.View
}
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*token_model.Token, error) {
token, err := repo.View.CreateToken(agentID, applicationID, userID, lifetime)
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, audience, scopes, lifetime)
if err != nil {
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) {
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
}

View File

@ -56,10 +56,18 @@ func (repo *UserRepo) ChangeMyEmail(ctx context.Context, email *model.Email) (*m
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 {
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 {
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
}
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) {
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)
}
@ -115,6 +140,19 @@ func (repo *UserRepo) RemoveMyMfaOTP(ctx context.Context) error {
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 {
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)
}
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 {
if obj.AggregateID != auth.GetCtxData(ctx).UserID {
return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user")

View 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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -1,9 +1,15 @@
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"
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
"time"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/eventstore/spooler"
usr_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
@ -11,7 +17,7 @@ import (
type Configs map[string]*Config
type Config struct {
MinimumCycleDurationMillisecond int
MinimumCycleDuration types.Duration
}
type handler struct {
@ -23,13 +29,27 @@ type handler struct {
type EventstoreRepos struct {
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{
&User{handler: handler{view, bulkLimit, configs.cycleDuration("User"), errorCount}},
&UserSession{handler: handler{view, bulkLimit, configs.cycleDuration("UserSession"), errorCount}, userEvents: repos.UserEvents},
&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 {
return 1 * time.Second
}
return time.Duration(c.MinimumCycleDurationMillisecond) * time.Millisecond
return c.MinimumCycleDuration.Duration
}

View 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)
}

View 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)
}

View File

@ -54,7 +54,9 @@ func (p *User) Process(event *models.Event) (err error) {
es_model.UserUnlocked,
es_model.MfaOtpAdded,
es_model.MfaOtpVerified,
es_model.MfaOtpRemoved:
es_model.MfaOtpRemoved,
es_model.MfaInitSkipped,
es_model.UserPasswordChanged:
user, err = p.view.UserByID(event.AggregateID)
if err != nil {
return err

View 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)
}

View File

@ -45,10 +45,8 @@ func (u *UserSession) Process(event *models.Event) (err error) {
switch event.Type {
case es_model.UserPasswordCheckSucceeded,
es_model.UserPasswordCheckFailed,
es_model.UserPasswordChanged,
es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed,
es_model.MfaOtpRemoved:
es_model.MfaOtpCheckFailed:
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
return err
@ -66,14 +64,22 @@ func (u *UserSession) Process(event *models.Event) (err error) {
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:
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 {
@ -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)
}
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)
if err != nil {
return err

View File

@ -2,6 +2,10 @@ package eventsourcing
import (
"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/handler"
@ -10,18 +14,23 @@ import (
"github.com/caos/zitadel/internal/auth_request/repository/cache"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/crypto"
es_int "github.com/caos/zitadel/internal/eventstore"
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
"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_proj "github.com/caos/zitadel/internal/project/repository/eventsourcing"
es_user "github.com/caos/zitadel/internal/user/repository/eventsourcing"
)
type Config struct {
SearchLimit uint64
Eventstore es_int.Config
AuthRequest cache.Config
View types.SQL
Spooler spooler.SpoolerConfig
KeyConfig es_key.KeyConfig
}
type EsRepository struct {
@ -29,9 +38,15 @@ type EsRepository struct {
eventstore.UserRepo
eventstore.AuthRequestRepo
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)
if err != nil {
return nil, err
@ -41,7 +56,14 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
if err != nil {
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 {
return nil, err
}
@ -70,8 +92,35 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
return nil, err
}
repos := handler.EventstoreRepos{UserEvents: user}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, repos)
key, err := es_key.StartKey(es, conf.KeyConfig, keyAlgorithm, idGenerator)
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{
spool,
@ -86,13 +135,41 @@ func Start(conf Config, systemDefaults sd.SystemDefaults) (*EsRepository, error)
View: view,
UserSessionViewProvider: view,
UserViewProvider: view,
IdGenerator: id.SonyFlakeGenerator,
UserEventProvider: user,
IdGenerator: idGenerator,
PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration,
MfaInitSkippedLifeTime: systemDefaults.VerificationLifetimes.MfaInitSkip.Duration,
MfaSoftwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaSoftwareCheck.Duration,
MfaHardwareCheckLifeTime: systemDefaults.VerificationLifetimes.MfaHardwareCheck.Duration,
},
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
}

View File

@ -2,6 +2,7 @@ package spooler
import (
"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/view"
@ -17,12 +18,12 @@ type SpoolerConfig struct {
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{
Eventstore: es,
Locker: &locker{dbClient: sql},
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.Start()

View 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
}

View 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)
}

View 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)
}

View File

@ -20,17 +20,23 @@ func (v *View) IsTokenValid(tokenID string) (bool, error) {
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()
token := &model.Token{
ID: id,
CreationDate: now,
UserID: userID,
ApplicationID: applicationID,
UserAgentID: agentID,
Scopes: scopes,
Audience: audience,
Expiration: now.Add(lifetime),
}
err := view.PutToken(v.Db, tokenTable, token)
if err != nil {
if err := view.PutToken(v.Db, tokenTable, token); err != nil {
return nil, err
}
return token, nil

View 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)
}

View File

@ -10,14 +10,14 @@ const (
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) {
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) {
return view.UserSessionsByAgentID(v.Db, userSessionTable, agentID)
}

View File

@ -4,19 +4,26 @@ import (
"database/sql"
"github.com/jinzhu/gorm"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/id"
)
type View struct {
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)
if err != nil {
return nil, err
}
return &View{
Db: gorm,
keyAlgorithm: keyAlgorithm,
idGenerator: idGenerator,
}, nil
}

View 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)
}

View 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)
}

View File

@ -9,4 +9,8 @@ type Repository interface {
UserRepository
AuthRequestRepository
TokenRepository
ApplicationRepository
KeyRepository
UserSessionRepository
UserGrantRepository
}

View File

@ -8,6 +8,7 @@ import (
)
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)
TokenByID(ctx context.Context, tokenID string) (*model.Token, error)
}

View File

@ -11,10 +11,20 @@ type UserRepository interface {
myUserRepo
SkipMfaInit(ctx context.Context, userID string) error
RequestPasswordReset(ctx context.Context, username 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
UserByID(ctx context.Context, userID string) (*model.User, error)
}
type myUserRepo interface {
@ -37,6 +47,6 @@ type myUserRepo interface {
ChangeMyPassword(ctx context.Context, old, new string) 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
}

View 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)
}

View 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)
}

View File

@ -2,6 +2,8 @@ package model
import (
"time"
"github.com/caos/zitadel/internal/errors"
)
type AuthRequest struct {
@ -17,14 +19,19 @@ type AuthRequest struct {
PossibleLOAs []LevelOfAssurance
UiLocales []string
LoginHint string
PreselectedUserID string
MaxAuthAge uint32
Request Request
levelOfAssurance LevelOfAssurance
projectApplicationIDs []string
UserID string
UserName string
UserOrgID string
PossibleSteps []NextStep
PasswordVerified bool
MfasVerified []MfaType
Audience []string
AuthTime time.Time
Code string
}
type Prompt int32
@ -56,12 +63,20 @@ func NewAuthRequest(id, agentID string, info *BrowserInfo, applicationID, callba
PossibleLOAs: possibleLOAs,
UiLocales: uiLocales,
LoginHint: loginHint,
PreselectedUserID: preselectedUserID,
UserID: preselectedUserID,
MaxAuthAge: maxAuthAge,
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 {
return a.ID != "" &&
a.AgentID != "" &&
@ -80,3 +95,9 @@ func (a *AuthRequest) WithCurrentInfo(info *BrowserInfo) *AuthRequest {
a.BrowserInfo = info
return a
}
func (a *AuthRequest) SetUserInfo(userID string, userName string, userOrgID string) {
a.UserID = userID
a.UserName = userName
a.UserOrgID = userOrgID
}

View File

@ -1,6 +1,12 @@
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 {
UserAgent string
@ -8,6 +14,14 @@ type BrowserInfo struct {
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 {
return i.UserAgent != "" &&
i.AcceptLanguage != "" &&

View File

@ -10,6 +10,7 @@ const (
NextStepUnspecified NextStepType = iota
NextStepLogin
NextStepUserSelection
NextStepInitUser
NextStepPassword
NextStepChangePassword
NextStepInitPassword
@ -26,9 +27,7 @@ const (
UserSessionStateTerminated
)
type LoginStep struct {
NotFound bool
}
type LoginStep struct{}
func (s *LoginStep) Type() NextStepType {
return NextStepLogin
@ -48,30 +47,33 @@ type UserSelection struct {
UserSessionState UserSessionState
}
type PasswordStep struct {
FailureCount uint16
type InitUserStep struct {
PasswordSet bool
}
func (s *InitUserStep) Type() NextStepType {
return NextStepInitUser
}
type PasswordStep struct{}
func (s *PasswordStep) Type() NextStepType {
return NextStepPassword
}
type ChangePasswordStep struct {
}
type ChangePasswordStep struct{}
func (s *ChangePasswordStep) Type() NextStepType {
return NextStepChangePassword
}
type InitPasswordStep struct {
}
type InitPasswordStep struct{}
func (s *InitPasswordStep) Type() NextStepType {
return NextStepInitPassword
}
type VerifyEMailStep struct {
}
type VerifyEMailStep struct{}
func (s *VerifyEMailStep) Type() NextStepType {
return NextStepVerifyEmail
@ -87,7 +89,6 @@ func (s *MfaPromptStep) Type() NextStepType {
}
type MfaVerificationStep struct {
FailureCount uint16
MfaProviders []MfaType
}
@ -95,8 +96,7 @@ func (s *MfaVerificationStep) Type() NextStepType {
return NextStepMfaVerify
}
type RedirectToCallbackStep struct {
}
type RedirectToCallbackStep struct{}
func (s *RedirectToCallbackStep) Type() NextStepType {
return NextStepRedirectToCallback
@ -111,7 +111,8 @@ const (
type MfaLevel int
const (
MfaLevelSoftware MfaLevel = iota
MfaLevelNotSetUp MfaLevel = iota
MfaLevelSoftware
MfaLevelHardware
MfaLevelHardwareCertified
)

View File

@ -7,6 +7,12 @@ type Request interface {
type AuthRequestType int32
var (
authRequestTypeMapping = map[AuthRequestType]Request{
AuthRequestTypeOIDC: &AuthRequestOIDC{},
}
)
const (
AuthRequestTypeOIDC AuthRequestType = iota
AuthRequestTypeSAML

View File

@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"github.com/caos/zitadel/internal/auth_request/model"
"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) {
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
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 errors.Is(err, sql.ErrNoRows) {
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")
}
request := new(model.AuthRequest)
err = json.Unmarshal(b, &request)
request, err := model.NewAuthRequestFromType(requestType)
if err == nil {
err = json.Unmarshal(b, request)
}
if err != nil {
return nil, caos_errs.ThrowInternal(err, "CACHE-2wshg", "unable to unmarshal auth request")
}
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)
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 {
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 {
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
}

View File

@ -1,3 +1,3 @@
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

View File

@ -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
}

View File

@ -1,5 +1,5 @@
// 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
@ -11,31 +11,60 @@ import (
reflect "reflect"
)
// MockRepository is a mock of Repository interface
type MockRepository struct {
// MockAuthRequestCache is a mock of AuthRequestCache interface
type MockAuthRequestCache struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
recorder *MockAuthRequestCacheMockRecorder
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository
type MockRepositoryMockRecorder struct {
mock *MockRepository
// MockAuthRequestCacheMockRecorder is the mock recorder for MockAuthRequestCache
type MockAuthRequestCacheMockRecorder struct {
mock *MockAuthRequestCache
}
// NewMockRepository creates a new mock instance
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
mock := &MockRepository{ctrl: ctrl}
mock.recorder = &MockRepositoryMockRecorder{mock}
// NewMockAuthRequestCache creates a new mock instance
func NewMockAuthRequestCache(ctrl *gomock.Controller) *MockAuthRequestCache {
mock := &MockAuthRequestCache{ctrl: ctrl}
mock.recorder = &MockAuthRequestCacheMockRecorder{mock}
return mock
}
// 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
}
// 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
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()
ret := m.ctrl.Call(m, "GetAuthRequestByID", arg0, arg1)
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
func (mr *MockRepositoryMockRecorder) GetAuthRequestByID(arg0, arg1 interface{}) *gomock.Call {
func (mr *MockAuthRequestCacheMockRecorder) GetAuthRequestByID(arg0, arg1 interface{}) *gomock.Call {
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
func (m *MockRepository) Health(arg0 context.Context) error {
func (m *MockAuthRequestCache) Health(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Health", arg0)
ret0, _ := ret[0].(error)
@ -58,22 +87,35 @@ func (m *MockRepository) Health(arg0 context.Context) error {
}
// 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()
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
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()
ret := m.ctrl.Call(m, "SaveAuthRequest", arg0, arg1)
ret0, _ := ret[0].(*model.AuthRequest)
ret1, _ := ret[1].(error)
return ret0, ret1
ret0, _ := ret[0].(error)
return ret0
}
// 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()
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)
}

View File

@ -6,9 +6,12 @@ import (
"github.com/caos/zitadel/internal/auth_request/model"
)
type Repository interface {
type AuthRequestCache interface {
Health(ctx context.Context) 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
View 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)
}

View 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)
}

View File

@ -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
}

View 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
}

View File

@ -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)
}

View 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
}

View 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)
}

View 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
}

View 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)
}

View 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)
}
})
}
}

View 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
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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()
}

View 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)
}

View File

@ -0,0 +1,11 @@
package repository
import (
"context"
)
type Repository interface {
Health(context.Context) error
UserGrantRepository
IamRepository
}

View 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)
}

View 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)
}

View File

@ -1,6 +1,9 @@
package crypto
import (
"database/sql/driver"
"encoding/json"
"github.com/caos/zitadel/internal/errors"
)
@ -35,6 +38,23 @@ type CryptoValue struct {
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
func Crypt(value []byte, c Crypto) (*CryptoValue, error) {

103
internal/crypto/rsa.go Normal file
View 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
View 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
View 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
}

View File

@ -65,7 +65,7 @@ func (i *Iam) AppendEvent(event *es_models.Event) (err error) {
i.SetUpDone = true
case IamProjectSet,
GlobalOrgSet:
err = i.setData(event)
err = i.SetData(event)
case IamMemberAdded:
err = i.appendAddMemberEvent(event)
case IamMemberChanged:
@ -76,7 +76,7 @@ func (i *Iam) AppendEvent(event *es_models.Event) (err error) {
return err
}
func (i *Iam) setData(event *es_models.Event) error {
func (i *Iam) SetData(event *es_models.Event) error {
i.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, i); err != nil {
logging.Log("EVEN-9sie4").WithError(err).Error("could not unmarshal event data")

View File

@ -56,7 +56,7 @@ func IamMemberToModel(member *IamMember) *model.IamMember {
func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
err := member.SetData(event)
if err != nil {
return err
}
@ -67,7 +67,7 @@ func (iam *Iam) appendAddMemberEvent(event *es_models.Event) error {
func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
err := member.SetData(event)
if err != nil {
return err
}
@ -79,7 +79,7 @@ func (iam *Iam) appendChangeMemberEvent(event *es_models.Event) error {
func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
member := &IamMember{}
err := member.setData(event)
err := member.SetData(event)
if err != nil {
return err
}
@ -91,7 +91,7 @@ func (iam *Iam) appendRemoveMemberEvent(event *es_models.Event) error {
return nil
}
func (m *IamMember) setData(event *es_models.Event) error {
func (m *IamMember) SetData(event *es_models.Event) error {
m.ObjectRoot.AppendEvent(event)
if err := json.Unmarshal(event.Data, m); err != nil {
logging.Log("EVEN-e4dkp").WithError(err).Error("could not unmarshal event data")

46
internal/key/model/key.go Normal file
View 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
}

View 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
}

View 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
}

View 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)
}
}

View 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
}

View 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