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
293 changed files with 14189 additions and 3176 deletions

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,8 +137,8 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, pas
if err != nil {
return err
}
if request.UserID == userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id ")
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 !checkLoggedIn && request.Prompt == model.PromptNone {
return append(steps, &model.RedirectToCallbackStep{}), nil
}
if request.UserID == "" {
if request.Prompt != model.PromptNone {
steps = append(steps, &model.LoginStep{})
}
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 {
return nil, err
if !errors.IsNotFound(err) {
return nil, err
}
session = &view_model.UserSessionView{}
}
return view_model.UserSessionToModel(session), nil
events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence)
if err != nil {
logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events")
return view_model.UserSessionToModel(session), nil
}
sessionCopy := *session
for _, event := range events {
switch event.Type {
case es_model.UserPasswordCheckSucceeded,
es_model.UserPasswordCheckFailed,
es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed:
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data")
return view_model.UserSessionToModel(session), nil
}
if eventData.UserAgentID != agentID {
continue
}
}
sessionCopy.AppendEvent(event)
}
return view_model.UserSessionToModel(&sessionCopy), nil
}
func userByID(provider userViewProvider, userID string) (*user_model.UserView, error) {
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
}
return view_model.UserToModel(user), nil
events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
if err != nil {
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events")
return view_model.UserToModel(user), nil
}
userCopy := *user
for _, event := range events {
if err := userCopy.AppendEvent(event); err != nil {
return view_model.UserToModel(user), nil
}
}
return view_model.UserToModel(&userCopy), nil
}

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,13 +121,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
View *view.View
userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider
userEventProvider userEventProvider
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
}
type args struct {
request *model.AuthRequest
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,
},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
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,7 +493,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
MfaMaxSetUp: model.MfaLevelSoftware,
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
},
@@ -391,7 +509,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
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 {
@@ -22,14 +28,28 @@ type handler struct {
}
type EventstoreRepos struct {
UserEvents *usr_event.UserEventstore
UserEvents *usr_event.UserEventstore
ProjectEvents *proj_event.ProjectEventstore
OrgEvents *org_events.OrgEventstore
IamEvents *iam_events.IamEventstore
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, repos EventstoreRepos) []spooler.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, eventstore eventstore.Eventstore, repos EventstoreRepos, systemDefaults sd.SystemDefaults) []spooler.Handler {
return []spooler.Handler{
&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
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,
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)
}