mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
feat: Login, OP Support and Auth Queries (#177)
* fix: change oidc config * fix: change oidc config secret * begin models * begin repo * fix: implement grpc app funcs * fix: add application requests * fix: converter * fix: converter * fix: converter and generate clientid * fix: tests * feat: project grant aggregate * feat: project grant * fix: project grant check if role existing * fix: project grant requests * fix: project grant fixes * fix: project grant member model * fix: project grant member aggregate * fix: project grant member eventstore * fix: project grant member requests * feat: user model * begin repo * repo models and more * feat: user command side * lots of functions * user command side * profile requests * commit before rebase on user * save * local config with gopass and more * begin new auth command (user centric) * Update internal/user/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/address.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/email.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/mfa.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/password.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/phone.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/model/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/usergrant/repository/eventsourcing/user_grant.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/user_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * Update internal/user/repository/eventsourcing/eventstore_mock_test.go Co-Authored-By: Livio Amstutz <livio.a@gmail.com> * changes from mr review * save files into basedir * changes from mr review * changes from mr review * move to auth request * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/usergrant/repository/eventsourcing/cache.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * changes requested on mr * fix generate codes * fix return if no events * password code * email verification step * more steps * lot of mfa * begin tests * more next steps * auth api * auth api (user) * auth api (user) * auth api (user) * differ requests * merge * tests * fix compilation error * mock for id generator * Update internal/user/repository/eventsourcing/model/password.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * Update internal/user/repository/eventsourcing/model/user.go Co-authored-by: Silvan <silvan.reusser@gmail.com> * requests of mr * check email * begin separation of command and query * otp * change packages * some cleanup and fixes * tests for auth request / next steps * add VerificationLifetimes to config and make it run * tests * fix code challenge validation * cleanup * fix merge * begin view * repackaging tests and configs * fix startup config for auth * add migration * add PromptSelectAccount * fix copy / paste * remove user_agent files * fixes * fix sequences in user_session * token commands * token queries and signout * fix * fix set password test * add token handler and table * handle session init * add session state * add user view test cases * change VerifyMyMfaOTP * some fixes * fix user repo in auth api * cleanup * add user session view test * fix merge * begin oidc * user agent and more * config * keys * key command and query * add login statics * key handler * start login * login handlers * lot of fixes * merge oidc * add missing exports * add missing exports * fix some bugs * authrequestid in htmls * getrequest * update auth request * fix userid check * add username to authrequest * fix user session and auth request handling * fix UserSessionsByAgentID * fix auth request tests * fix user session on UserPasswordChanged and MfaOtpRemoved * fix MfaTypesSetupPossible * handle mfa * fill username * auth request query checks new events * fix userSessionByIDs * fix tokens * fix userSessionByIDs test * add user selection * init code * user code creation date * add init user step * add verification failed types * add verification failures * verify init code * user init code handle * user init code handle * fix userSessionByIDs * update logging * user agent cookie * browserinfo from request * add DeleteAuthRequest * add static login files to binary * add login statik to build * move generate to separate file and remove statik.go files * remove static dirs from startup.yaml * generate into separate namespaces * merge master * auth request code * auth request type mapping * fix keys * improve tokens * improve register and basic styling * fix ailerons font * improve password reset * add audience to token * all oidc apps as audience * fix test nextStep * fix email texts * remove "not set" * lot of style changes * improve copy to clipboard * fix footer * add cookie handler * remove placeholders * fix compilation after merge * fix auth config * remove comments * typo * use new secrets store * change default pws to match default policy * fixes * add todo * enable login * fix db name * Auth queries (#179) * my usersession * org structure/ auth handlers * working user grant spooler * auth internal user grants * search my project orgs * remove permissions file * my zitadel permissions * my zitadel permissions * remove unused code * authz * app searches in view * token verification * fix user grant load * fix tests * fix tests * read configs * remove unused const * remove todos * env variables * app_name * working authz * search projects * global resourceowner * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * Update internal/api/auth/permissions.go Co-authored-by: Livio Amstutz <livio.a@gmail.com> * model2 rename * at least it works * check token expiry * search my user grants * remove token table from authz Co-authored-by: Livio Amstutz <livio.a@gmail.com> * fix test * fix ports and enable console Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com> Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
12
internal/auth/repository/application.go
Normal file
12
internal/auth/repository/application.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
)
|
||||
|
||||
type ApplicationRepository interface {
|
||||
ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error)
|
||||
AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error
|
||||
}
|
@@ -9,7 +9,12 @@ import (
|
||||
type AuthRequestRepository interface {
|
||||
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
|
||||
}
|
||||
|
@@ -0,0 +1,31 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/project/model"
|
||||
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
)
|
||||
|
||||
type ApplicationRepo struct {
|
||||
View *view.View
|
||||
ProjectEvents *proj_event.ProjectEventstore
|
||||
}
|
||||
|
||||
func (a *ApplicationRepo) ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error) {
|
||||
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proj_view_model.ApplicationViewToModel(app), nil
|
||||
}
|
||||
|
||||
func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error {
|
||||
app, err := a.View.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.ProjectEvents.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
|
||||
}
|
@@ -4,23 +4,28 @@ import (
|
||||
"context"
|
||||
"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
|
||||
}
|
||||
|
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
16
internal/auth/repository/eventsourcing/eventstore/iam.go
Normal file
16
internal/auth/repository/eventsourcing/eventstore/iam.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||
)
|
||||
|
||||
type IamRepository struct {
|
||||
IamID string
|
||||
IamEvents *iam_event.IamEventstore
|
||||
}
|
||||
|
||||
func (repo *IamRepository) GetIam(ctx context.Context) (*model.Iam, error) {
|
||||
return repo.IamEvents.IamByID(ctx, repo.IamID)
|
||||
}
|
75
internal/auth/repository/eventsourcing/eventstore/key.go
Normal file
75
internal/auth/repository/eventsourcing/eventstore/key.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/key/model"
|
||||
key_event "github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||
)
|
||||
|
||||
const (
|
||||
oidcUser = "OIDC"
|
||||
iamOrg = "IAM"
|
||||
)
|
||||
|
||||
type KeyRepository struct {
|
||||
KeyEvents *key_event.KeyEventstore
|
||||
View *view.View
|
||||
SigningKeyRotation time.Duration
|
||||
}
|
||||
|
||||
func (k *KeyRepository) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||
ctx = setOIDCCtx(ctx)
|
||||
_, err := k.KeyEvents.GenerateKeyPair(ctx, model.KeyUsageSigning, algorithm)
|
||||
return err
|
||||
}
|
||||
|
||||
func (k *KeyRepository) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, renewTimer <-chan time.Time) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-renewTimer:
|
||||
k.refreshSigningKey(keyCh, errCh)
|
||||
renewTimer = time.After(k.SigningKeyRotation)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (k *KeyRepository) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) {
|
||||
keys, err := k.View.GetActiveKeySet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webKeys := make([]jose.JSONWebKey, len(keys))
|
||||
for i, key := range keys {
|
||||
webKeys[i] = jose.JSONWebKey{KeyID: key.ID, Algorithm: key.Algorithm, Use: key.Usage.String(), Key: key.Key}
|
||||
}
|
||||
return &jose.JSONWebKeySet{Keys: webKeys}, nil
|
||||
}
|
||||
|
||||
func (k *KeyRepository) refreshSigningKey(keyCh chan<- jose.SigningKey, errCh chan<- error) {
|
||||
key, err := k.View.GetSigningKey()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
keyCh <- jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(key.Algorithm),
|
||||
Key: jose.JSONWebKey{
|
||||
KeyID: key.ID,
|
||||
Key: key.Key,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func setOIDCCtx(ctx context.Context) context.Context {
|
||||
return auth.SetCtxData(ctx, auth.CtxData{UserID: oidcUser, OrgID: iamOrg})
|
||||
}
|
29
internal/auth/repository/eventsourcing/eventstore/org.go
Normal file
29
internal/auth/repository/eventsourcing/eventstore/org.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
"github.com/caos/zitadel/internal/org/repository/view"
|
||||
)
|
||||
|
||||
type OrgRepository struct {
|
||||
SearchLimit uint64
|
||||
*org_es.OrgEventstore
|
||||
View *auth_view.View
|
||||
}
|
||||
|
||||
func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) {
|
||||
request.EnsureLimit(repo.SearchLimit)
|
||||
members, count, err := repo.View.SearchOrgs(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &org_model.OrgSearchResult{
|
||||
Offset: request.Offset,
|
||||
Limit: request.Limit,
|
||||
TotalResult: uint64(count),
|
||||
Result: view.OrgsToModel(members),
|
||||
}, nil
|
||||
}
|
@@ -13,8 +13,8 @@ type TokenRepo struct {
|
||||
View *view.View
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@@ -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")
|
||||
|
158
internal/auth/repository/eventsourcing/eventstore/user_grant.go
Normal file
158
internal/auth/repository/eventsourcing/eventstore/user_grant.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_view "github.com/caos/zitadel/internal/org/repository/view"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||
)
|
||||
|
||||
type UserGrantRepo struct {
|
||||
SearchLimit uint64
|
||||
View *view.View
|
||||
IamID string
|
||||
Auth auth.Config
|
||||
AuthZRepo *authz_repo.EsRepository
|
||||
}
|
||||
|
||||
func (repo *UserGrantRepo) SearchMyUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) {
|
||||
request.EnsureLimit(repo.SearchLimit)
|
||||
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_USER_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: auth.GetCtxData(ctx).UserID})
|
||||
grants, count, err := repo.View.SearchUserGrants(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &grant_model.UserGrantSearchResponse{
|
||||
Offset: request.Offset,
|
||||
Limit: request.Limit,
|
||||
TotalResult: uint64(count),
|
||||
Result: model.UserGrantsToModel(grants),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
|
||||
request.EnsureLimit(repo.SearchLimit)
|
||||
ctxData := auth.GetCtxData(ctx)
|
||||
if ctxData.ProjectID == "" {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "APP-7lqva", "Could not get ProjectID")
|
||||
}
|
||||
if ctxData.ProjectID == repo.AuthZRepo.IamProjectID {
|
||||
isAdmin, err := repo.IsIamAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isAdmin {
|
||||
return repo.SearchAdminOrgs(request)
|
||||
}
|
||||
}
|
||||
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_PROJECT_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: ctxData.ProjectID})
|
||||
|
||||
grants, err := repo.SearchMyUserGrants(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return grantRespToOrgResp(grants), nil
|
||||
}
|
||||
|
||||
func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]string, error) {
|
||||
grant, err := repo.AuthZRepo.ResolveGrants(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
permissions := &grant_model.Permissions{Permissions: []string{}}
|
||||
for _, role := range grant.Roles {
|
||||
roleName, ctxID := auth.SplitPermission(role)
|
||||
for _, mapping := range repo.Auth.RolePermissionMappings {
|
||||
if mapping.Role == roleName {
|
||||
permissions.AppendPermissions(ctxID, mapping.Permissions...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissions.Permissions, nil
|
||||
}
|
||||
|
||||
func (repo *UserGrantRepo) SearchAdminOrgs(request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
|
||||
searchRequest := &org_model.OrgSearchRequest{}
|
||||
if len(request.Queries) > 0 {
|
||||
for _, q := range request.Queries {
|
||||
if q.Key == grant_model.USERGRANTSEARCHKEY_ORG_NAME {
|
||||
searchRequest.Queries = append(searchRequest.Queries, &org_model.OrgSearchQuery{Key: org_model.ORGSEARCHKEY_ORG_NAME, Method: q.Method, Value: q.Value})
|
||||
}
|
||||
}
|
||||
}
|
||||
orgs, count, err := repo.View.SearchOrgs(searchRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return orgRespToOrgResp(orgs, count), nil
|
||||
}
|
||||
|
||||
func (repo *UserGrantRepo) IsIamAdmin(ctx context.Context) (bool, error) {
|
||||
grantSearch := &grant_model.UserGrantSearchRequest{
|
||||
Queries: []*grant_model.UserGrantSearchQuery{
|
||||
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_RESOURCEOWNER, Method: global_model.SEARCHMETHOD_EQUALS, Value: repo.IamID},
|
||||
}}
|
||||
result, err := repo.SearchMyUserGrants(ctx, grantSearch)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if result.TotalResult == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_model.ProjectOrgSearchResponse {
|
||||
resp := &grant_model.ProjectOrgSearchResponse{
|
||||
TotalResult: grants.TotalResult,
|
||||
}
|
||||
resp.Result = make([]*grant_model.Org, len(grants.Result))
|
||||
for i, g := range grants.Result {
|
||||
resp.Result[i] = &grant_model.Org{OrgID: g.ResourceOwner, OrgName: g.OrgName}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func orgRespToOrgResp(orgs []*org_view.OrgView, count int) *grant_model.ProjectOrgSearchResponse {
|
||||
resp := &grant_model.ProjectOrgSearchResponse{
|
||||
TotalResult: uint64(count),
|
||||
}
|
||||
resp.Result = make([]*grant_model.Org, len(orgs))
|
||||
for i, o := range orgs {
|
||||
resp.Result[i] = &grant_model.Org{OrgID: o.ID, OrgName: o.Name}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func mergeOrgAndAdminGrant(ctxData auth.CtxData, orgGrant, iamAdminGrant *model.UserGrantView) (grant *auth.Grant) {
|
||||
if orgGrant != nil {
|
||||
roles := orgGrant.RoleKeys
|
||||
if iamAdminGrant != nil {
|
||||
roles = addIamAdminRoles(roles, iamAdminGrant.RoleKeys)
|
||||
}
|
||||
grant = &auth.Grant{OrgID: orgGrant.ResourceOwner, Roles: roles}
|
||||
} else if iamAdminGrant != nil {
|
||||
grant = &auth.Grant{
|
||||
OrgID: ctxData.OrgID,
|
||||
Roles: iamAdminGrant.RoleKeys,
|
||||
}
|
||||
}
|
||||
return grant
|
||||
}
|
||||
|
||||
func addIamAdminRoles(orgRoles, iamAdminRoles []string) []string {
|
||||
result := make([]string, 0)
|
||||
result = append(result, iamAdminRoles...)
|
||||
for _, role := range orgRoles {
|
||||
if !auth.ExistsPerm(result, role) {
|
||||
result = append(result, role)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
"github.com/caos/zitadel/internal/user/repository/view/model"
|
||||
)
|
||||
|
||||
type UserSessionRepo struct {
|
||||
View *view.View
|
||||
}
|
||||
|
||||
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
|
||||
userSessions, err := repo.View.UserSessionsByUserID(auth.GetCtxData(ctx).UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model.UserSessionsToModel(userSessions), nil
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||
view_model "github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
handler
|
||||
projectEvents *proj_event.ProjectEventstore
|
||||
}
|
||||
|
||||
const (
|
||||
applicationTable = "auth.applications"
|
||||
)
|
||||
|
||||
func (p *Application) MinimumCycleDuration() time.Duration { return p.cycleDuration }
|
||||
|
||||
func (p *Application) ViewModel() string {
|
||||
return applicationTable
|
||||
}
|
||||
|
||||
func (p *Application) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := p.view.GetLatestApplicationSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return eventsourcing.ProjectQuery(sequence), nil
|
||||
}
|
||||
|
||||
func (p *Application) Process(event *models.Event) (err error) {
|
||||
app := new(view_model.ApplicationView)
|
||||
switch event.Type {
|
||||
case es_model.ApplicationAdded:
|
||||
app.AppendEvent(event)
|
||||
case es_model.ApplicationChanged,
|
||||
es_model.OIDCConfigAdded,
|
||||
es_model.OIDCConfigChanged,
|
||||
es_model.ApplicationDeactivated,
|
||||
es_model.ApplicationReactivated:
|
||||
err := app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app, err = p.view.ApplicationByID(app.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app.AppendEvent(event)
|
||||
case es_model.ApplicationRemoved:
|
||||
err := app.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.DeleteApplication(app.ID, event.Sequence)
|
||||
default:
|
||||
return p.view.ProcessedApplicationSequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.view.PutApplication(app)
|
||||
}
|
||||
|
||||
func (p *Application) OnError(event *models.Event, spoolerError error) error {
|
||||
logging.LogWithFields("SPOOL-ls9ew", "id", event.AggregateID).WithError(spoolerError).Warn("something went wrong in project app handler")
|
||||
return spooler.HandleError(event, spoolerError, p.view.GetLatestApplicationFailedEvent, p.view.ProcessedApplicationFailedEvent, p.view.ProcessedApplicationSequence, p.errorCountUntilSkip)
|
||||
}
|
@@ -1,9 +1,15 @@
|
||||
package handler
|
||||
|
||||
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
|
||||
}
|
||||
|
55
internal/auth/repository/eventsourcing/handler/key.go
Normal file
55
internal/auth/repository/eventsourcing/handler/key.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
es_model "github.com/caos/zitadel/internal/key/repository/eventsourcing/model"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||
view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
||||
)
|
||||
|
||||
type Key struct {
|
||||
handler
|
||||
}
|
||||
|
||||
const (
|
||||
keyTable = "auth.keys"
|
||||
)
|
||||
|
||||
func (k *Key) MinimumCycleDuration() time.Duration { return k.cycleDuration }
|
||||
|
||||
func (k *Key) ViewModel() string {
|
||||
return keyTable
|
||||
}
|
||||
|
||||
func (k *Key) EventQuery() (*models.SearchQuery, error) {
|
||||
sequence, err := k.view.GetLatestKeySequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return eventsourcing.KeyPairQuery(sequence), nil
|
||||
}
|
||||
|
||||
func (k *Key) Process(event *models.Event) error {
|
||||
switch event.Type {
|
||||
case es_model.KeyPairAdded:
|
||||
privateKey, publicKey, err := view_model.KeysFromPairEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return k.view.PutKeys(privateKey, publicKey, event.Sequence)
|
||||
default:
|
||||
return k.view.ProcessedKeySequence(event.Sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Key) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-GHa3a", "id", event.AggregateID).WithError(err).Warn("something went wrong in key handler")
|
||||
return spooler.HandleError(event, err, k.view.GetLatestKeyFailedEvent, k.view.ProcessedKeyFailedEvent, k.view.ProcessedKeySequence, k.errorCountUntilSkip)
|
||||
}
|
64
internal/auth/repository/eventsourcing/handler/org.go
Normal file
64
internal/auth/repository/eventsourcing/handler/org.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/view"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Org struct {
|
||||
handler
|
||||
}
|
||||
|
||||
const (
|
||||
orgTable = "auth.orgs"
|
||||
)
|
||||
|
||||
func (o *Org) MinimumCycleDuration() time.Duration { return o.cycleDuration }
|
||||
|
||||
func (o *Org) ViewModel() string {
|
||||
return orgTable
|
||||
}
|
||||
|
||||
func (o *Org) EventQuery() (*es_models.SearchQuery, error) {
|
||||
sequence, err := o.view.GetLatestOrgSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return eventsourcing.OrgQuery(sequence), nil
|
||||
}
|
||||
|
||||
func (o *Org) Process(event *es_models.Event) error {
|
||||
org := new(view.OrgView)
|
||||
|
||||
switch event.Type {
|
||||
case model.OrgAdded:
|
||||
org.AppendEvent(event)
|
||||
case model.OrgChanged:
|
||||
err := org.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err = o.view.OrgByID(org.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = org.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return o.view.ProcessedOrgSequence(event.Sequence)
|
||||
}
|
||||
|
||||
return o.view.PutOrg(org)
|
||||
}
|
||||
|
||||
func (o *Org) OnError(event *es_models.Event, spoolerErr error) error {
|
||||
logging.LogWithFields("SPOOL-8siWS", "id", event.AggregateID).WithError(spoolerErr).Warn("something went wrong in org handler")
|
||||
return spooler.HandleError(event, spoolerErr, o.view.GetLatestOrgFailedEvent, o.view.ProcessedOrgFailedEvent, o.view.ProcessedOrgSequence, o.errorCountUntilSkip)
|
||||
}
|
@@ -54,7 +54,9 @@ func (p *User) Process(event *models.Event) (err error) {
|
||||
es_model.UserUnlocked,
|
||||
es_model.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
|
||||
|
344
internal/auth/repository/eventsourcing/handler/user_grant.go
Normal file
344
internal/auth/repository/eventsourcing/handler/user_grant.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
iam_events "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
org_events "github.com/caos/zitadel/internal/org/repository/eventsourcing"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
|
||||
proj_es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
||||
usr_model "github.com/caos/zitadel/internal/user/model"
|
||||
usr_events "github.com/caos/zitadel/internal/user/repository/eventsourcing"
|
||||
usr_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
|
||||
grant_es_model "github.com/caos/zitadel/internal/usergrant/repository/eventsourcing/model"
|
||||
view_model "github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserGrant struct {
|
||||
handler
|
||||
eventstore eventstore.Eventstore
|
||||
projectEvents *proj_event.ProjectEventstore
|
||||
userEvents *usr_events.UserEventstore
|
||||
orgEvents *org_events.OrgEventstore
|
||||
iamEvents *iam_events.IamEventstore
|
||||
iamID string
|
||||
iamProjectID string
|
||||
}
|
||||
|
||||
const (
|
||||
userGrantTable = "auth.user_grants"
|
||||
)
|
||||
|
||||
func (u *UserGrant) MinimumCycleDuration() time.Duration { return u.cycleDuration }
|
||||
|
||||
func (u *UserGrant) ViewModel() string {
|
||||
return userGrantTable
|
||||
}
|
||||
|
||||
func (u *UserGrant) EventQuery() (*models.SearchQuery, error) {
|
||||
if u.iamProjectID == "" {
|
||||
err := u.setIamProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
sequence, err := u.view.GetLatestUserGrantSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return es_models.NewSearchQuery().
|
||||
AggregateTypeFilter(grant_es_model.UserGrantAggregate, iam_es_model.IamAggregate, org_es_model.OrgAggregate, usr_es_model.UserAggregate, proj_es_model.ProjectAggregate).
|
||||
LatestSequenceFilter(sequence), nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) Process(event *models.Event) (err error) {
|
||||
switch event.AggregateType {
|
||||
case grant_es_model.UserGrantAggregate:
|
||||
err = u.processUserGrant(event)
|
||||
case usr_es_model.UserAggregate:
|
||||
err = u.processUser(event)
|
||||
case proj_es_model.ProjectAggregate:
|
||||
err = u.processProject(event)
|
||||
case iam_es_model.IamAggregate:
|
||||
err = u.processIamMember(event, "IAM", false)
|
||||
case org_es_model.OrgAggregate:
|
||||
return u.processOrg(event)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *UserGrant) processUserGrant(event *models.Event) (err error) {
|
||||
grant := new(view_model.UserGrantView)
|
||||
switch event.Type {
|
||||
case grant_es_model.UserGrantAdded:
|
||||
err = grant.AppendEvent(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = u.fillData(grant, event.ResourceOwner)
|
||||
case grant_es_model.UserGrantChanged,
|
||||
grant_es_model.UserGrantDeactivated,
|
||||
grant_es_model.UserGrantReactivated:
|
||||
grant, err = u.view.UserGrantByID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = grant.AppendEvent(event)
|
||||
case grant_es_model.UserGrantRemoved:
|
||||
err = u.view.DeleteUserGrant(event.AggregateID, event.Sequence)
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.view.PutUserGrant(grant, grant.Sequence)
|
||||
}
|
||||
|
||||
func (u *UserGrant) processUser(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case usr_es_model.UserProfileChanged,
|
||||
usr_es_model.UserEmailChanged:
|
||||
grants, err := u.view.UserGrantsByUserID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, err := u.userEvents.UserByID(context.Background(), event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, grant := range grants {
|
||||
u.fillUserData(grant, user)
|
||||
err = u.view.PutUserGrant(grant, event.Sequence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) processProject(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case proj_es_model.ProjectChanged:
|
||||
grants, err := u.view.UserGrantsByProjectID(event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project, err := u.projectEvents.ProjectByID(context.Background(), event.AggregateID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, grant := range grants {
|
||||
u.fillProjectData(grant, project)
|
||||
return u.view.PutUserGrant(grant, event.Sequence)
|
||||
}
|
||||
case proj_es_model.ProjectMemberAdded, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectMemberRemoved:
|
||||
member := new(proj_es_model.ProjectMember)
|
||||
member.SetData(event)
|
||||
return u.processMember(event, "PROJECT", true, member.UserID, member.Roles)
|
||||
case proj_es_model.ProjectGrantMemberAdded, proj_es_model.ProjectGrantMemberChanged, proj_es_model.ProjectGrantMemberRemoved:
|
||||
member := new(proj_es_model.ProjectGrantMember)
|
||||
member.SetData(event)
|
||||
return u.processMember(event, "PROJECT_GRANT", true, member.UserID, member.Roles)
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) processOrg(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case org_es_model.OrgMemberAdded, org_es_model.OrgMemberChanged, org_es_model.OrgMemberRemoved:
|
||||
member := new(org_es_model.OrgMember)
|
||||
member.SetData(event)
|
||||
return u.processMember(event, "ORG", false, member.UserID, member.Roles)
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) processIamMember(event *models.Event, rolePrefix string, suffix bool) error {
|
||||
member := new(iam_es_model.IamMember)
|
||||
|
||||
switch event.Type {
|
||||
case iam_es_model.IamMemberAdded, iam_es_model.IamMemberChanged:
|
||||
member.SetData(event)
|
||||
|
||||
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if errors.IsNotFound(err) {
|
||||
grant = &view_model.UserGrantView{
|
||||
ID: u.iamProjectID + member.UserID,
|
||||
ResourceOwner: u.iamID,
|
||||
OrgName: u.iamID,
|
||||
OrgDomain: u.iamID,
|
||||
ProjectID: u.iamProjectID,
|
||||
UserID: member.UserID,
|
||||
RoleKeys: member.Roles,
|
||||
CreationDate: event.CreationDate,
|
||||
}
|
||||
if suffix {
|
||||
grant.RoleKeys = suffixRoles(event.AggregateID, grant.RoleKeys)
|
||||
}
|
||||
} else {
|
||||
newRoles := member.Roles
|
||||
if grant.RoleKeys != nil {
|
||||
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||
} else {
|
||||
grant.RoleKeys = newRoles
|
||||
}
|
||||
|
||||
}
|
||||
grant.Sequence = event.Sequence
|
||||
grant.ChangeDate = event.CreationDate
|
||||
return u.view.PutUserGrant(grant, grant.Sequence)
|
||||
case iam_es_model.IamMemberRemoved:
|
||||
member.SetData(event)
|
||||
grant, err := u.view.UserGrantByIDs(u.iamID, u.iamProjectID, member.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UserGrant) processMember(event *models.Event, rolePrefix string, suffix bool, userID string, roleKeys []string) error {
|
||||
switch event.Type {
|
||||
case org_es_model.OrgMemberAdded, proj_es_model.ProjectMemberAdded, proj_es_model.ProjectGrantMemberAdded,
|
||||
org_es_model.OrgMemberChanged, proj_es_model.ProjectMemberChanged, proj_es_model.ProjectGrantMemberChanged:
|
||||
|
||||
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||
if err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
if suffix {
|
||||
roleKeys = suffixRoles(event.AggregateID, roleKeys)
|
||||
}
|
||||
if errors.IsNotFound(err) {
|
||||
grant = &view_model.UserGrantView{
|
||||
ID: u.iamProjectID + event.ResourceOwner + userID,
|
||||
ResourceOwner: event.ResourceOwner,
|
||||
ProjectID: u.iamProjectID,
|
||||
UserID: userID,
|
||||
RoleKeys: roleKeys,
|
||||
CreationDate: event.CreationDate,
|
||||
}
|
||||
u.fillData(grant, event.ResourceOwner)
|
||||
} else {
|
||||
newRoles := roleKeys
|
||||
if grant.RoleKeys != nil {
|
||||
grant.RoleKeys = mergeExistingRoles(rolePrefix, grant.RoleKeys, newRoles)
|
||||
} else {
|
||||
grant.RoleKeys = newRoles
|
||||
}
|
||||
}
|
||||
grant.Sequence = event.Sequence
|
||||
grant.ChangeDate = event.CreationDate
|
||||
return u.view.PutUserGrant(grant, event.Sequence)
|
||||
case org_es_model.OrgMemberRemoved,
|
||||
proj_es_model.ProjectMemberRemoved,
|
||||
proj_es_model.ProjectGrantMemberRemoved:
|
||||
|
||||
grant, err := u.view.UserGrantByIDs(event.ResourceOwner, u.iamProjectID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.view.DeleteUserGrant(grant.ID, event.Sequence)
|
||||
default:
|
||||
return u.view.ProcessedUserGrantSequence(event.Sequence)
|
||||
}
|
||||
}
|
||||
|
||||
func suffixRoles(suffix string, roles []string) []string {
|
||||
suffixedRoles := make([]string, len(roles))
|
||||
for i := 0; i < len(roles); i++ {
|
||||
suffixedRoles[i] = roles[i] + ":" + suffix
|
||||
}
|
||||
return suffixedRoles
|
||||
}
|
||||
|
||||
func mergeExistingRoles(rolePrefix string, existingRoles, newRoles []string) []string {
|
||||
mergedRoles := make([]string, 0)
|
||||
for _, existing := range existingRoles {
|
||||
if !strings.HasPrefix(existing, rolePrefix) {
|
||||
mergedRoles = append(mergedRoles, existing)
|
||||
}
|
||||
}
|
||||
return append(mergedRoles, newRoles...)
|
||||
}
|
||||
|
||||
func (u *UserGrant) setIamProjectID() error {
|
||||
if u.iamProjectID != "" {
|
||||
return nil
|
||||
}
|
||||
iam, err := u.iamEvents.IamByID(context.Background(), u.iamID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !iam.SetUpDone {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "HANDL-s5DTs", "Setup not done")
|
||||
}
|
||||
u.iamProjectID = iam.IamProjectID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) fillData(grant *view_model.UserGrantView, resourceOwner string) (err error) {
|
||||
user, err := u.userEvents.UserByID(context.Background(), grant.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.fillUserData(grant, user)
|
||||
project, err := u.projectEvents.ProjectByID(context.Background(), grant.ProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.fillProjectData(grant, project)
|
||||
|
||||
org, err := u.orgEvents.OrgByID(context.TODO(), org_model.NewOrg(resourceOwner))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.fillOrgData(grant, org)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserGrant) fillUserData(grant *view_model.UserGrantView, user *usr_model.User) {
|
||||
grant.UserName = user.UserName
|
||||
grant.FirstName = user.FirstName
|
||||
grant.LastName = user.LastName
|
||||
grant.Email = user.EmailAddress
|
||||
}
|
||||
|
||||
func (u *UserGrant) fillProjectData(grant *view_model.UserGrantView, project *proj_model.Project) {
|
||||
grant.ProjectName = project.Name
|
||||
}
|
||||
|
||||
func (u *UserGrant) fillOrgData(grant *view_model.UserGrantView, org *org_model.Org) {
|
||||
grant.OrgDomain = org.Domain
|
||||
grant.OrgName = org.Name
|
||||
}
|
||||
|
||||
func (u *UserGrant) OnError(event *models.Event, err error) error {
|
||||
logging.LogWithFields("SPOOL-8is4s", "id", event.AggregateID).WithError(err).Warn("something went wrong in user handler")
|
||||
return spooler.HandleError(event, err, u.view.GetLatestUserGrantFailedEvent, u.view.ProcessedUserGrantFailedEvent, u.view.ProcessedUserGrantSequence, u.errorCountUntilSkip)
|
||||
}
|
@@ -45,10 +45,8 @@ func (u *UserSession) Process(event *models.Event) (err error) {
|
||||
switch event.Type {
|
||||
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
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
105
internal/auth/repository/eventsourcing/view/application.go
Normal file
105
internal/auth/repository/eventsourcing/view/application.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
global_model "github.com/caos/zitadel/internal/model"
|
||||
proj_model "github.com/caos/zitadel/internal/project/model"
|
||||
"github.com/caos/zitadel/internal/project/repository/view"
|
||||
"github.com/caos/zitadel/internal/project/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view"
|
||||
)
|
||||
|
||||
const (
|
||||
applicationTable = "auth.applications"
|
||||
)
|
||||
|
||||
func (v *View) ApplicationByID(appID string) (*model.ApplicationView, error) {
|
||||
return view.ApplicationByID(v.Db, applicationTable, appID)
|
||||
}
|
||||
|
||||
func (v *View) SearchApplications(request *proj_model.ApplicationSearchRequest) ([]*model.ApplicationView, int, error) {
|
||||
return view.SearchApplications(v.Db, applicationTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutApplication(project *model.ApplicationView) error {
|
||||
err := view.PutApplication(v.Db, applicationTable, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedApplicationSequence(project.Sequence)
|
||||
}
|
||||
|
||||
func (v *View) DeleteApplication(appID string, eventSequence uint64) error {
|
||||
err := view.DeleteApplication(v.Db, applicationTable, appID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedApplicationSequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationSequence() (uint64, error) {
|
||||
return v.latestSequence(applicationTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationSequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(applicationTable, eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestApplicationFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(applicationTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedApplicationFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
||||
|
||||
func (v *View) ApplicationByClientID(_ context.Context, clientID string) (*model.ApplicationView, error) {
|
||||
req := &proj_model.ApplicationSearchRequest{
|
||||
Limit: 1,
|
||||
Queries: []*proj_model.ApplicationSearchQuery{
|
||||
{
|
||||
Key: proj_model.APPLICATIONSEARCHKEY_OIDC_CLIENT_ID,
|
||||
Method: global_model.SEARCHMETHOD_EQUALS,
|
||||
Value: clientID,
|
||||
},
|
||||
},
|
||||
}
|
||||
apps, count, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "VIEW-sd6JQ", "cannot find client")
|
||||
}
|
||||
if count != 1 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "VIEW-dfw3as", "cannot find client")
|
||||
}
|
||||
return apps[0], nil
|
||||
}
|
||||
|
||||
func (v *View) AppIDsFromProjectByClientID(ctx context.Context, clientID string) ([]string, error) {
|
||||
app, err := v.ApplicationByClientID(ctx, clientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &proj_model.ApplicationSearchRequest{
|
||||
Queries: []*proj_model.ApplicationSearchQuery{
|
||||
{
|
||||
Key: proj_model.APPLICATIONSEARCHKEY_PROJECT_ID,
|
||||
Method: global_model.SEARCHMETHOD_EQUALS,
|
||||
Value: app.ProjectID,
|
||||
},
|
||||
},
|
||||
}
|
||||
apps, _, err := view.SearchApplications(v.Db, applicationTable, req)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowPreconditionFailed(err, "VIEW-Gd24q", "cannot find applications")
|
||||
}
|
||||
ids := make([]string, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
if !app.IsOIDC {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, app.OIDCClientID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
72
internal/auth/repository/eventsourcing/view/key.go
Normal file
72
internal/auth/repository/eventsourcing/view/key.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
"github.com/caos/zitadel/internal/key/repository/view"
|
||||
"github.com/caos/zitadel/internal/key/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view"
|
||||
)
|
||||
|
||||
const (
|
||||
keyTable = "auth.keys"
|
||||
)
|
||||
|
||||
func (v *View) KeyByIDAndType(keyID string, private bool) (*model.KeyView, error) {
|
||||
return view.KeyByIDAndType(v.Db, keyTable, keyID, private)
|
||||
}
|
||||
|
||||
func (v *View) GetSigningKey() (*key_model.SigningKey, error) {
|
||||
key, err := view.GetSigningKey(v.Db, keyTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key_model.SigningKeyFromKeyView(model.KeyViewToModel(key), v.keyAlgorithm)
|
||||
}
|
||||
|
||||
func (v *View) GetActiveKeySet() ([]*key_model.PublicKey, error) {
|
||||
keys, err := view.GetActivePublicKeys(v.Db, keyTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key_model.PublicKeysFromKeyView(model.KeyViewsToModel(keys), v.keyAlgorithm)
|
||||
}
|
||||
|
||||
func (v *View) PutKeys(privateKey, publicKey *model.KeyView, eventSequence uint64) error {
|
||||
err := view.PutKeys(v.Db, keyTable, privateKey, publicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedKeySequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) DeleteKey(keyID string, private bool, eventSequence uint64) error {
|
||||
err := view.DeleteKey(v.Db, keyTable, keyID, private)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedKeySequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) DeleteKeyPair(keyID string, eventSequence uint64) error {
|
||||
err := view.DeleteKeyPair(v.Db, keyTable, keyID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedKeySequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestKeySequence() (uint64, error) {
|
||||
return v.latestSequence(keyTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedKeySequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(keyTable, eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestKeyFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(keyTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedKeyFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
43
internal/auth/repository/eventsourcing/view/org.go
Normal file
43
internal/auth/repository/eventsourcing/view/org.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/org/model"
|
||||
org_view "github.com/caos/zitadel/internal/org/repository/view"
|
||||
"github.com/caos/zitadel/internal/view"
|
||||
)
|
||||
|
||||
const (
|
||||
orgTable = "auth.orgs"
|
||||
)
|
||||
|
||||
func (v *View) OrgByID(orgID string) (*org_view.OrgView, error) {
|
||||
return org_view.OrgByID(v.Db, orgTable, orgID)
|
||||
}
|
||||
|
||||
func (v *View) SearchOrgs(req *model.OrgSearchRequest) ([]*org_view.OrgView, int, error) {
|
||||
return org_view.SearchOrgs(v.Db, orgTable, req)
|
||||
}
|
||||
|
||||
func (v *View) PutOrg(org *org_view.OrgView) error {
|
||||
err := org_view.PutOrg(v.Db, orgTable, org)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedOrgSequence(org.Sequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestOrgFailedEvent(sequence uint64) (*view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(orgTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedOrgFailedEvent(failedEvent *view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestOrgSequence() (uint64, error) {
|
||||
return v.latestSequence(orgTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedOrgSequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(orgTable, eventSequence)
|
||||
}
|
@@ -20,17 +20,23 @@ func (v *View) IsTokenValid(tokenID string) (bool, error) {
|
||||
return view.IsTokenValid(v.Db, tokenTable, tokenID)
|
||||
}
|
||||
|
||||
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
|
||||
|
64
internal/auth/repository/eventsourcing/view/user_grant.go
Normal file
64
internal/auth/repository/eventsourcing/view/user_grant.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
"github.com/caos/zitadel/internal/usergrant/repository/view"
|
||||
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
|
||||
global_view "github.com/caos/zitadel/internal/view"
|
||||
)
|
||||
|
||||
const (
|
||||
userGrantTable = "auth.user_grants"
|
||||
)
|
||||
|
||||
func (v *View) UserGrantByID(grantID string) (*model.UserGrantView, error) {
|
||||
return view.UserGrantByID(v.Db, userGrantTable, grantID)
|
||||
}
|
||||
|
||||
func (v *View) UserGrantByIDs(resourceOwnerID, projectID, userID string) (*model.UserGrantView, error) {
|
||||
return view.UserGrantByIDs(v.Db, userGrantTable, resourceOwnerID, projectID, userID)
|
||||
}
|
||||
|
||||
func (v *View) UserGrantsByUserID(userID string) ([]*model.UserGrantView, error) {
|
||||
return view.UserGrantsByUserID(v.Db, userGrantTable, userID)
|
||||
}
|
||||
|
||||
func (v *View) UserGrantsByProjectID(projectID string) ([]*model.UserGrantView, error) {
|
||||
return view.UserGrantsByProjectID(v.Db, userGrantTable, projectID)
|
||||
}
|
||||
|
||||
func (v *View) SearchUserGrants(request *grant_model.UserGrantSearchRequest) ([]*model.UserGrantView, int, error) {
|
||||
return view.SearchUserGrants(v.Db, userGrantTable, request)
|
||||
}
|
||||
|
||||
func (v *View) PutUserGrant(grant *model.UserGrantView, sequence uint64) error {
|
||||
err := view.PutUserGrant(v.Db, userGrantTable, grant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return v.ProcessedUserGrantSequence(sequence)
|
||||
}
|
||||
|
||||
func (v *View) DeleteUserGrant(grantID string, eventSequence uint64) error {
|
||||
err := view.DeleteUserGrant(v.Db, userGrantTable, grantID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return v.ProcessedUserGrantSequence(eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserGrantSequence() (uint64, error) {
|
||||
return v.latestSequence(userGrantTable)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedUserGrantSequence(eventSequence uint64) error {
|
||||
return v.saveCurrentSequence(userGrantTable, eventSequence)
|
||||
}
|
||||
|
||||
func (v *View) GetLatestUserGrantFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
|
||||
return v.latestFailedEvent(userGrantTable, sequence)
|
||||
}
|
||||
|
||||
func (v *View) ProcessedUserGrantFailedEvent(failedEvent *global_view.FailedEvent) error {
|
||||
return v.saveFailedEvent(failedEvent)
|
||||
}
|
@@ -10,14 +10,14 @@ const (
|
||||
userSessionTable = "auth.user_sessions"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
11
internal/auth/repository/iam.go
Normal file
11
internal/auth/repository/iam.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
type IamRepository interface {
|
||||
GetIam(ctx context.Context) (*model.Iam, error)
|
||||
}
|
14
internal/auth/repository/key.go
Normal file
14
internal/auth/repository/key.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type KeyRepository interface {
|
||||
GenerateSigningKeyPair(ctx context.Context, algorithm string) error
|
||||
GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, timer <-chan time.Time)
|
||||
GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error)
|
||||
}
|
@@ -9,4 +9,8 @@ type Repository interface {
|
||||
UserRepository
|
||||
AuthRequestRepository
|
||||
TokenRepository
|
||||
ApplicationRepository
|
||||
KeyRepository
|
||||
UserSessionRepository
|
||||
UserGrantRepository
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
12
internal/auth/repository/user_grant.go
Normal file
12
internal/auth/repository/user_grant.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/usergrant/model"
|
||||
)
|
||||
|
||||
type UserGrantRepository interface {
|
||||
SearchMyUserGrants(ctx context.Context, request *model.UserGrantSearchRequest) (*model.UserGrantSearchResponse, error)
|
||||
SearchMyProjectOrgs(ctx context.Context, request *model.UserGrantSearchRequest) (*model.ProjectOrgSearchResponse, error)
|
||||
SearchMyZitadelPermissions(ctx context.Context) ([]string, error)
|
||||
}
|
10
internal/auth/repository/user_session.go
Normal file
10
internal/auth/repository/user_session.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/caos/zitadel/internal/user/model"
|
||||
)
|
||||
|
||||
type UserSessionRepository interface {
|
||||
GetMyUserSessions(ctx context.Context) ([]*model.UserSessionView, error)
|
||||
}
|
Reference in New Issue
Block a user