feat: Login, OP Support and Auth Queries (#177)

* fix: change oidc config

* fix: change oidc config secret

* begin models

* begin repo

* fix: implement grpc app funcs

* fix: add application requests

* fix: converter

* fix: converter

* fix: converter and generate clientid

* fix: tests

* feat: project grant aggregate

* feat: project grant

* fix: project grant check if role existing

* fix: project grant requests

* fix: project grant fixes

* fix: project grant member model

* fix: project grant member aggregate

* fix: project grant member eventstore

* fix: project grant member requests

* feat: user model

* begin repo

* repo models and more

* feat: user command side

* lots of functions

* user command side

* profile requests

* commit before rebase on user

* save

* local config with gopass and more

* begin new auth command (user centric)

* Update internal/user/model/user.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/address.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/address.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/email.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/email.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/email.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/mfa.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/mfa.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/password.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/password.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/password.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/phone.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/phone.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/phone.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/user.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/user.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/model/user.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/usergrant/repository/eventsourcing/model/user_grant.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/usergrant/repository/eventsourcing/model/user_grant.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/usergrant/repository/eventsourcing/user_grant.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/user_test.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* Update internal/user/repository/eventsourcing/eventstore_mock_test.go

Co-Authored-By: Livio Amstutz <livio.a@gmail.com>

* changes from mr review

* save files into basedir

* changes from mr review

* changes from mr review

* move to auth request

* Update internal/usergrant/repository/eventsourcing/cache.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update internal/usergrant/repository/eventsourcing/cache.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* changes requested on mr

* fix generate codes

* fix return if no events

* password code

* email verification step

* more steps

* lot of mfa

* begin tests

* more next steps

* auth api

* auth api (user)

* auth api (user)

* auth api (user)

* differ requests

* merge

* tests

* fix compilation error

* mock for id generator

* Update internal/user/repository/eventsourcing/model/password.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* Update internal/user/repository/eventsourcing/model/user.go

Co-authored-by: Silvan <silvan.reusser@gmail.com>

* requests of mr

* check email

* begin separation of command and query

* otp

* change packages

* some cleanup and fixes

* tests for auth request / next steps

* add VerificationLifetimes to config and make it run

* tests

* fix code challenge validation

* cleanup

* fix merge

* begin view

* repackaging tests and configs

* fix startup config for auth

* add migration

* add PromptSelectAccount

* fix copy / paste

* remove user_agent files

* fixes

* fix sequences in user_session

* token commands

* token queries and signout

* fix

* fix set password test

* add token handler and table

* handle session init

* add session state

* add user view test cases

* change VerifyMyMfaOTP

* some fixes

* fix user repo in auth api

* cleanup

* add user session view test

* fix merge

* begin oidc

* user agent and more

* config

* keys

* key command and query

* add login statics

* key handler

* start login

* login handlers

* lot of fixes

* merge oidc

* add missing exports

* add missing exports

* fix some bugs

* authrequestid in htmls

* getrequest

* update auth request

* fix userid check

* add username to authrequest

* fix user session and auth request handling

* fix UserSessionsByAgentID

* fix auth request tests

* fix user session on UserPasswordChanged and MfaOtpRemoved

* fix MfaTypesSetupPossible

* handle mfa

* fill username

* auth request query checks new events

* fix userSessionByIDs

* fix tokens

* fix userSessionByIDs test

* add user selection

* init code

* user code creation date

* add init user step

* add verification failed types

* add verification failures

* verify init code

* user init code handle

* user init code handle

* fix userSessionByIDs

* update logging

* user agent cookie

* browserinfo from request

* add DeleteAuthRequest

* add static login files to binary

* add login statik to build

* move generate to separate file and remove statik.go files

* remove static dirs from startup.yaml

* generate into separate namespaces

* merge master

* auth request code

* auth request type mapping

* fix keys

* improve tokens

* improve register and basic styling

* fix ailerons font

* improve password reset

* add audience to token

* all oidc apps as audience

* fix test nextStep

* fix email texts

* remove "not set"

* lot of style changes

* improve copy to clipboard

* fix footer

* add cookie handler

* remove placeholders

* fix compilation after merge

* fix auth config

* remove comments

* typo

* use new secrets store

* change default pws to match default policy

* fixes

* add todo

* enable login

* fix db name

* Auth queries (#179)

* my usersession

* org structure/ auth handlers

* working user grant spooler

* auth internal user grants

* search my project orgs

* remove permissions file

* my zitadel permissions

* my zitadel permissions

* remove unused code

* authz

* app searches in view

* token verification

* fix user grant load

* fix tests

* fix tests

* read configs

* remove unused const

* remove todos

* env variables

* app_name

* working authz

* search projects

* global resourceowner

* Update internal/api/auth/permissions.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* Update internal/api/auth/permissions.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* model2 rename

* at least it works

* check token expiry

* search my user grants

* remove token table from authz

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* fix test

* fix ports and enable console

Co-authored-by: Fabiennne <fabienne.gerschwiler@gmail.com>
Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Livio Amstutz
2020-06-05 07:50:04 +02:00
committed by GitHub
parent 46b60a6968
commit 8a5badddf6
293 changed files with 14189 additions and 3176 deletions

View File

@@ -0,0 +1,31 @@
package eventstore
import (
"context"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/project/model"
proj_event "github.com/caos/zitadel/internal/project/repository/eventsourcing"
proj_view_model "github.com/caos/zitadel/internal/project/repository/view/model"
)
type ApplicationRepo struct {
View *view.View
ProjectEvents *proj_event.ProjectEventstore
}
func (a *ApplicationRepo) ApplicationByClientID(ctx context.Context, clientID string) (*model.ApplicationView, error) {
app, err := a.View.ApplicationByClientID(ctx, clientID)
if err != nil {
return nil, err
}
return proj_view_model.ApplicationViewToModel(app), nil
}
func (a *ApplicationRepo) AuthorizeOIDCApplication(ctx context.Context, clientID, secret string) error {
app, err := a.View.ApplicationByClientID(ctx, clientID)
if err != nil {
return err
}
return a.ProjectEvents.VerifyOIDCClientSecret(ctx, app.ProjectID, app.ID, secret)
}

View File

@@ -4,23 +4,28 @@ import (
"context"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
cache "github.com/caos/zitadel/internal/auth_request/repository"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/id"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
type AuthRequestRepo struct {
UserEvents *user_event.UserEventstore
AuthRequests *cache.AuthRequestCache
AuthRequests cache.AuthRequestCache
View *view.View
UserSessionViewProvider userSessionViewProvider
UserViewProvider userViewProvider
UserEventProvider userEventProvider
IdGenerator id.Generator
@@ -38,6 +43,10 @@ type userViewProvider interface {
UserByID(string) (*view_model.UserView, error)
}
type userEventProvider interface {
UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error)
}
func (repo *AuthRequestRepo) Health(ctx context.Context) error {
if err := repo.UserEvents.Health(ctx); err != nil {
return err
@@ -51,6 +60,11 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
return nil, err
}
request.ID = reqID
ids, err := repo.View.AppIDsFromProjectByClientID(ctx, request.ApplicationID)
if err != nil {
return nil, err
}
request.Audience = ids
err = repo.AuthRequests.SaveAuthRequest(ctx, request)
if err != nil {
return nil, err
@@ -59,11 +73,28 @@ func (repo *AuthRequestRepo) CreateAuthRequest(ctx context.Context, request *mod
}
func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*model.AuthRequest, error) {
return repo.getAuthRequest(ctx, id, false)
}
func (repo *AuthRequestRepo) AuthRequestByIDCheckLoggedIn(ctx context.Context, id string) (*model.AuthRequest, error) {
return repo.getAuthRequest(ctx, id, true)
}
func (repo *AuthRequestRepo) SaveAuthCode(ctx context.Context, id, code string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return err
}
request.Code = code
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) AuthRequestByCode(ctx context.Context, code string) (*model.AuthRequest, error) {
request, err := repo.AuthRequests.GetAuthRequestByCode(ctx, code)
if err != nil {
return nil, err
}
steps, err := repo.nextSteps(request)
steps, err := repo.nextSteps(ctx, request, true)
if err != nil {
return nil, err
}
@@ -71,6 +102,10 @@ func (repo *AuthRequestRepo) AuthRequestByID(ctx context.Context, id string) (*m
return request, nil
}
func (repo *AuthRequestRepo) DeleteAuthRequest(ctx context.Context, id string) error {
return repo.AuthRequests.DeleteAuthRequest(ctx, id)
}
func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
@@ -80,8 +115,21 @@ func (repo *AuthRequestRepo) CheckUsername(ctx context.Context, id, username str
if err != nil {
return err
}
request.UserID = user.ID
return repo.AuthRequests.SaveAuthRequest(ctx, request)
request.SetUserInfo(user.ID, user.UserName, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) SelectUser(ctx context.Context, id, userID string) error {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return err
}
user, err := repo.View.UserByID(userID)
if err != nil {
return err
}
request.SetUserInfo(user.ID, user.UserName, user.ResourceOwner)
return repo.AuthRequests.UpdateAuthRequest(ctx, request)
}
func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, password string, info *model.BrowserInfo) error {
@@ -89,8 +137,8 @@ func (repo *AuthRequestRepo) VerifyPassword(ctx context.Context, id, userID, pas
if err != nil {
return err
}
if request.UserID == userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id ")
if request.UserID != userID {
return errors.ThrowPreconditionFailed(nil, "EVENT-ds35D", "user id does not match request id")
}
return repo.UserEvents.CheckPassword(ctx, userID, password, request.WithCurrentInfo(info))
}
@@ -106,15 +154,29 @@ func (repo *AuthRequestRepo) VerifyMfaOTP(ctx context.Context, authRequestID, us
return repo.UserEvents.CheckMfaOTP(ctx, userID, code, request.WithCurrentInfo(info))
}
func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.NextStep, error) {
func (repo *AuthRequestRepo) getAuthRequest(ctx context.Context, id string, checkLoggedIn bool) (*model.AuthRequest, error) {
request, err := repo.AuthRequests.GetAuthRequestByID(ctx, id)
if err != nil {
return nil, err
}
steps, err := repo.nextSteps(ctx, request, checkLoggedIn)
if err != nil {
return nil, err
}
request.PossibleSteps = steps
return request, nil
}
func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *model.AuthRequest, checkLoggedIn bool) ([]model.NextStep, error) {
if request == nil {
return nil, errors.ThrowInvalidArgument(nil, "EVENT-ds27a", "request must not be nil")
}
steps := make([]model.NextStep, 0)
if !checkLoggedIn && request.Prompt == model.PromptNone {
return append(steps, &model.RedirectToCallbackStep{}), nil
}
if request.UserID == "" {
if request.Prompt != model.PromptNone {
steps = append(steps, &model.LoginStep{})
}
steps = append(steps, &model.LoginStep{})
if request.Prompt == model.PromptSelectAccount {
users, err := repo.usersForUserSelection(request)
if err != nil {
@@ -124,15 +186,18 @@ func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.Next
}
return steps, nil
}
userSession, err := userSessionByIDs(repo.UserSessionViewProvider, request.AgentID, request.UserID)
user, err := userByID(ctx, repo.UserViewProvider, repo.UserEventProvider, request.UserID)
if err != nil {
return nil, err
}
user, err := userByID(repo.UserViewProvider, request.UserID)
userSession, err := userSessionByIDs(ctx, repo.UserSessionViewProvider, repo.UserEventProvider, request.AgentID, user)
if err != nil {
return nil, err
}
if user.InitRequired {
return append(steps, &model.InitUserStep{PasswordSet: user.PasswordSet}), nil
}
if !user.PasswordSet {
return append(steps, &model.InitPasswordStep{}), nil
}
@@ -140,6 +205,8 @@ func (repo *AuthRequestRepo) nextSteps(request *model.AuthRequest) ([]model.Next
if !checkVerificationTime(userSession.PasswordVerification, repo.PasswordCheckLifeTime) {
return append(steps, &model.PasswordStep{}), nil
}
request.PasswordVerified = true
request.AuthTime = userSession.PasswordVerification
if step, ok := repo.mfaChecked(userSession, request, user); !ok {
return append(steps, step), nil
@@ -178,23 +245,32 @@ func (repo *AuthRequestRepo) usersForUserSelection(request *model.AuthRequest) (
func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView, request *model.AuthRequest, user *user_model.UserView) (model.NextStep, bool) {
mfaLevel := request.MfaLevel()
required := user.MfaMaxSetUp < mfaLevel
if required || !repo.mfaSkippedOrSetUp(user) {
promptRequired := user.MfaMaxSetUp < mfaLevel
if promptRequired || !repo.mfaSkippedOrSetUp(user) {
return &model.MfaPromptStep{
Required: required,
Required: promptRequired,
MfaProviders: user.MfaTypesSetupPossible(mfaLevel),
}, false
}
switch mfaLevel {
default:
fallthrough
case model.MfaLevelNotSetUp:
if user.MfaMaxSetUp == model.MfaLevelNotSetUp {
return nil, true
}
fallthrough
case model.MfaLevelSoftware:
if checkVerificationTime(userSession.MfaSoftwareVerification, repo.MfaSoftwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaSoftwareVerificationType)
request.AuthTime = userSession.MfaSoftwareVerification
return nil, true
}
fallthrough
case model.MfaLevelHardware:
if checkVerificationTime(userSession.MfaHardwareVerification, repo.MfaHardwareCheckLifeTime) {
request.MfasVerified = append(request.MfasVerified, userSession.MfaHardwareVerificationType)
request.AuthTime = userSession.MfaHardwareVerification
return nil, true
}
}
@@ -204,7 +280,7 @@ func (repo *AuthRequestRepo) mfaChecked(userSession *user_model.UserSessionView,
}
func (repo *AuthRequestRepo) mfaSkippedOrSetUp(user *user_model.UserView) bool {
if user.MfaMaxSetUp >= 0 {
if user.MfaMaxSetUp > model.MfaLevelNotSetUp {
return true
}
return checkVerificationTime(user.MfaInitSkipped, repo.MfaInitSkippedLifeTime)
@@ -222,18 +298,55 @@ func userSessionsByUserAgentID(provider userSessionViewProvider, agentID string)
return view_model.UserSessionsToModel(session), nil
}
func userSessionByIDs(provider userSessionViewProvider, agentID, userID string) (*user_model.UserSessionView, error) {
session, err := provider.UserSessionByIDs(agentID, userID)
func userSessionByIDs(ctx context.Context, provider userSessionViewProvider, eventProvider userEventProvider, agentID string, user *user_model.UserView) (*user_model.UserSessionView, error) {
session, err := provider.UserSessionByIDs(agentID, user.ID)
if err != nil {
return nil, err
if !errors.IsNotFound(err) {
return nil, err
}
session = &view_model.UserSessionView{}
}
return view_model.UserSessionToModel(session), nil
events, err := eventProvider.UserEventsByID(ctx, user.ID, session.Sequence)
if err != nil {
logging.Log("EVENT-Hse6s").WithError(err).Debug("error retrieving new events")
return view_model.UserSessionToModel(session), nil
}
sessionCopy := *session
for _, event := range events {
switch event.Type {
case es_model.UserPasswordCheckSucceeded,
es_model.UserPasswordCheckFailed,
es_model.MfaOtpCheckSucceeded,
es_model.MfaOtpCheckFailed:
eventData, err := view_model.UserSessionFromEvent(event)
if err != nil {
logging.Log("EVENT-sdgT3").WithError(err).Debug("error getting event data")
return view_model.UserSessionToModel(session), nil
}
if eventData.UserAgentID != agentID {
continue
}
}
sessionCopy.AppendEvent(event)
}
return view_model.UserSessionToModel(&sessionCopy), nil
}
func userByID(provider userViewProvider, userID string) (*user_model.UserView, error) {
user, err := provider.UserByID(userID)
func userByID(ctx context.Context, viewProvider userViewProvider, eventProvider userEventProvider, userID string) (*user_model.UserView, error) {
user, err := viewProvider.UserByID(userID)
if err != nil {
return nil, err
}
return view_model.UserToModel(user), nil
events, err := eventProvider.UserEventsByID(ctx, userID, user.Sequence)
if err != nil {
logging.Log("EVENT-dfg42").WithError(err).Debug("error retrieving new events")
return view_model.UserToModel(user), nil
}
userCopy := *user
for _, event := range events {
if err := userCopy.AppendEvent(event); err != nil {
return view_model.UserToModel(user), nil
}
}
return view_model.UserToModel(&userCopy), nil
}

View File

@@ -1,6 +1,8 @@
package eventstore
import (
"context"
"encoding/json"
"testing"
"time"
@@ -10,8 +12,10 @@ import (
"github.com/caos/zitadel/internal/auth_request/model"
"github.com/caos/zitadel/internal/auth_request/repository/cache"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
user_model "github.com/caos/zitadel/internal/user/model"
user_event "github.com/caos/zitadel/internal/user/repository/eventsourcing"
user_es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
view_model "github.com/caos/zitadel/internal/user/repository/view/model"
)
@@ -25,6 +29,16 @@ func (m *mockViewNoUserSession) UserSessionsByAgentID(string) ([]*view_model.Use
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockViewErrUserSession struct{}
func (m *mockViewErrUserSession) UserSessionByIDs(string, string) (*view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
func (m *mockViewErrUserSession) UserSessionsByAgentID(string) ([]*view_model.UserSessionView, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockViewUserSession struct {
PasswordVerification time.Time
MfaSoftwareVerification time.Time
@@ -60,7 +74,26 @@ func (m *mockViewNoUser) UserByID(string) (*view_model.UserView, error) {
return nil, errors.ThrowNotFound(nil, "id", "user not found")
}
type mockEventUser struct {
Event *es_models.Event
}
func (m *mockEventUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
events := make([]*es_models.Event, 0)
if m.Event != nil {
events = append(events, m.Event)
}
return events, nil
}
type mockEventErrUser struct{}
func (m *mockEventErrUser) UserEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
return nil, errors.ThrowInternal(nil, "id", "internal error")
}
type mockViewUser struct {
InitRequired bool
PasswordSet bool
PasswordChangeRequired bool
IsEmailVerified bool
@@ -71,6 +104,7 @@ type mockViewUser struct {
func (m *mockViewUser) UserByID(string) (*view_model.UserView, error) {
return &view_model.UserView{
InitRequired: m.InitRequired,
PasswordSet: m.PasswordSet,
PasswordChangeRequired: m.PasswordChangeRequired,
IsEmailVerified: m.IsEmailVerified,
@@ -87,13 +121,15 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
View *view.View
userSessionViewProvider userSessionViewProvider
userViewProvider userViewProvider
userEventProvider userEventProvider
PasswordCheckLifeTime time.Duration
MfaInitSkippedLifeTime time.Duration
MfaSoftwareCheckLifeTime time.Duration
MfaHardwareCheckLifeTime time.Duration
}
type args struct {
request *model.AuthRequest
request *model.AuthRequest
checkLoggedIn bool
}
tests := []struct {
name string
@@ -105,22 +141,22 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
{
"request nil, error",
fields{},
args{nil},
args{nil, false},
nil,
errors.IsErrorInvalidArgument,
},
{
"user not set, login step",
"prompt none and checkLoggedIn false, callback step",
fields{},
args{&model.AuthRequest{}},
[]model.NextStep{&model.LoginStep{}},
args{&model.AuthRequest{Prompt: model.PromptNone}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
{
"user not set and prompt none, no step",
"user not set, login step",
fields{},
args{&model.AuthRequest{Prompt: model.PromptNone}},
[]model.NextStep{},
args{&model.AuthRequest{}, false},
[]model.NextStep{&model.LoginStep{}},
nil,
},
{
@@ -128,7 +164,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
fields{
userSessionViewProvider: &mockViewNoUserSession{},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false},
nil,
errors.IsInternal,
},
@@ -147,8 +183,9 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
},
},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}},
args{&model.AuthRequest{Prompt: model.PromptSelectAccount}, false},
[]model.NextStep{
&model.LoginStep{},
&model.SelectUserStep{
@@ -166,31 +203,63 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
nil,
},
{
"usersession not found, not found error",
"user not not found, not found error",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
userViewProvider: &mockViewNoUser{},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
errors.IsNotFound,
},
{
"user not not found, not found error",
"usersession not found, new user session, password step",
fields{
userSessionViewProvider: &mockViewNoUserSession{},
userViewProvider: &mockViewUser{
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
{
"usersession error, internal error",
fields{
userSessionViewProvider: &mockViewErrUserSession{},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}, false},
nil,
errors.IsInternal,
},
{
"user not initialized, init user step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewNoUser{},
userViewProvider: &mockViewUser{
InitRequired: true,
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitUserStep{
PasswordSet: true,
}},
nil,
errors.IsNotFound,
},
{
"password not set, init password step",
fields{
userSessionViewProvider: &mockViewUserSession{},
userViewProvider: &mockViewUser{},
userEventProvider: &mockEventUser{},
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.InitPasswordStep{}},
nil,
},
@@ -201,9 +270,10 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.PasswordStep{}},
nil,
},
@@ -218,10 +288,11 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
OTPState: int32(user_model.MFASTATE_READY),
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.MfaVerificationStep{
MfaProviders: []model.MfaType{model.MfaTypeOTP},
}},
@@ -238,11 +309,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
PasswordSet: true,
PasswordChangeRequired: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.ChangePasswordStep{}},
nil,
},
@@ -255,11 +328,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
},
userViewProvider: &mockViewUser{
PasswordSet: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.VerifyEMailStep{}},
nil,
},
@@ -273,11 +348,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
PasswordChangeRequired: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.ChangePasswordStep{}, &model.VerifyEMailStep{}},
nil,
},
@@ -291,11 +368,33 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID"}},
args{&model.AuthRequest{UserID: "UserID"}, false},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
{
"prompt none, checkLoggedIn true and authenticated, redirect to callback step",
fields{
userSessionViewProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Add(-5 * time.Minute),
MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Minute),
},
userViewProvider: &mockViewUser{
PasswordSet: true,
IsEmailVerified: true,
MfaMaxSetUp: int32(model.MfaLevelSoftware),
},
userEventProvider: &mockEventUser{},
PasswordCheckLifeTime: 10 * 24 * time.Hour,
MfaSoftwareCheckLifeTime: 18 * time.Hour,
},
args{&model.AuthRequest{UserID: "UserID", Prompt: model.PromptNone}, true},
[]model.NextStep{&model.RedirectToCallbackStep{}},
nil,
},
@@ -308,12 +407,13 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) {
View: tt.fields.View,
UserSessionViewProvider: tt.fields.userSessionViewProvider,
UserViewProvider: tt.fields.userViewProvider,
UserEventProvider: tt.fields.userEventProvider,
PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime,
MfaInitSkippedLifeTime: tt.fields.MfaInitSkippedLifeTime,
MfaSoftwareCheckLifeTime: tt.fields.MfaSoftwareCheckLifeTime,
MfaHardwareCheckLifeTime: tt.fields.MfaHardwareCheckLifeTime,
}
got, err := repo.nextSteps(tt.args.request)
got, err := repo.nextSteps(context.Background(), tt.args.request, tt.args.checkLoggedIn)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
t.Errorf("nextSteps() wrong error = %v", err)
return
@@ -360,14 +460,31 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: -1,
MfaMaxSetUp: model.MfaLevelNotSetUp,
},
},
&model.MfaPromptStep{
MfaProviders: []model.MfaType{},
MfaProviders: []model.MfaType{
model.MfaTypeOTP,
},
},
false,
},
{
"not set up and skipped, true",
fields{
MfaInitSkippedLifeTime: 30 * 24 * time.Hour,
},
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
MfaMaxSetUp: model.MfaLevelNotSetUp,
MfaInitSkipped: time.Now().UTC(),
},
},
nil,
true,
},
{
"checked mfa software, true",
fields{
@@ -376,7 +493,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
MfaMaxSetUp: model.MfaLevelSoftware,
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{MfaSoftwareVerification: time.Now().UTC().Add(-5 * time.Hour)},
},
@@ -391,7 +509,8 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) {
args{
request: &model.AuthRequest{},
user: &user_model.UserView{
OTPState: user_model.MFASTATE_READY,
MfaMaxSetUp: model.MfaLevelSoftware,
OTPState: user_model.MFASTATE_READY,
},
userSession: &user_model.UserSessionView{},
},
@@ -473,3 +592,235 @@ func TestAuthRequestRepo_mfaSkippedOrSetUp(t *testing.T) {
})
}
}
func Test_userSessionByIDs(t *testing.T) {
type args struct {
userProvider userSessionViewProvider
eventProvider userEventProvider
agentID string
user *user_model.UserView
}
tests := []struct {
name string
args args
want *user_model.UserSessionView
wantErr func(error) bool
}{
{
"not found, new session",
args{
userProvider: &mockViewNoUserSession{},
eventProvider: &mockEventErrUser{},
user: &user_model.UserView{ID: "id"},
},
&user_model.UserSessionView{},
nil,
},
{
"internal error, internal error",
args{
userProvider: &mockViewErrUserSession{},
user: &user_model.UserView{ID: "id"},
},
nil,
errors.IsInternal,
},
{
"error user events, old view model state",
args{
userProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
},
user: &user_model.UserView{ID: "id"},
eventProvider: &mockEventErrUser{},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
},
nil,
},
{
"new user events but error, old view model state",
args{
userProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
},
agentID: "agentID",
user: &user_model.UserView{ID: "id"},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.MfaOtpCheckSucceeded,
CreationDate: time.Now().UTC().Round(1 * time.Second),
},
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
},
nil,
},
{
"new user events but other agentID, old view model state",
args{
userProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
},
agentID: "agentID",
user: &user_model.UserView{ID: "id"},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.MfaOtpCheckSucceeded,
CreationDate: time.Now().UTC().Round(1 * time.Second),
Data: func() []byte {
data, _ := json.Marshal(&user_es_model.AuthRequest{UserAgentID: "otherID"})
return data
}(),
},
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Time{},
MfaHardwareVerification: time.Time{},
},
nil,
},
{
"new user events, new view model state",
args{
userProvider: &mockViewUserSession{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
},
agentID: "agentID",
user: &user_model.UserView{ID: "id"},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.MfaOtpCheckSucceeded,
CreationDate: time.Now().UTC().Round(1 * time.Second),
Data: func() []byte {
data, _ := json.Marshal(&user_es_model.AuthRequest{UserAgentID: "agentID"})
return data
}(),
},
},
},
&user_model.UserSessionView{
PasswordVerification: time.Now().UTC().Round(1 * time.Second),
MfaSoftwareVerification: time.Now().UTC().Round(1 * time.Second),
ChangeDate: time.Now().UTC().Round(1 * time.Second),
},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := userSessionByIDs(context.Background(), tt.args.userProvider, tt.args.eventProvider, tt.args.agentID, tt.args.user)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
t.Errorf("nextSteps() wrong error = %v", err)
return
}
assert.Equal(t, tt.want, got)
})
}
}
func Test_userByID(t *testing.T) {
type args struct {
ctx context.Context
viewProvider userViewProvider
eventProvider userEventProvider
userID string
}
tests := []struct {
name string
args args
want *user_model.UserView
wantErr func(error) bool
}{
{
"not found, not found error",
args{
viewProvider: &mockViewNoUser{},
},
nil,
errors.IsNotFound,
},
{
"error user events, old view model state",
args{
viewProvider: &mockViewUser{
PasswordChangeRequired: true,
},
eventProvider: &mockEventErrUser{},
},
&user_model.UserView{
PasswordChangeRequired: true,
},
nil,
},
{
"new user events but error, old view model state",
args{
viewProvider: &mockViewUser{
PasswordChangeRequired: true,
},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.UserPasswordChanged,
CreationDate: time.Now().UTC().Round(1 * time.Second),
Data: nil,
},
},
},
&user_model.UserView{
PasswordChangeRequired: true,
},
nil,
},
{
"new user events, new view model state",
args{
viewProvider: &mockViewUser{
PasswordChangeRequired: true,
},
eventProvider: &mockEventUser{
&es_models.Event{
AggregateType: user_es_model.UserAggregate,
Type: user_es_model.UserPasswordChanged,
CreationDate: time.Now().UTC().Round(1 * time.Second),
Data: func() []byte {
data, _ := json.Marshal(user_es_model.Password{ChangeRequired: false})
return data
}(),
},
},
},
&user_model.UserView{
PasswordChangeRequired: false,
ChangeDate: time.Now().UTC().Round(1 * time.Second),
State: user_model.USERSTATE_INITIAL,
PasswordChanged: time.Now().UTC().Round(1 * time.Second),
},
nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := userByID(tt.args.ctx, tt.args.viewProvider, tt.args.eventProvider, tt.args.userID)
if (err != nil && tt.wantErr == nil) || (tt.wantErr != nil && !tt.wantErr(err)) {
t.Errorf("nextSteps() wrong error = %v", err)
return
}
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,16 @@
package eventstore
import (
"context"
"github.com/caos/zitadel/internal/iam/model"
iam_event "github.com/caos/zitadel/internal/iam/repository/eventsourcing"
)
type IamRepository struct {
IamID string
IamEvents *iam_event.IamEventstore
}
func (repo *IamRepository) GetIam(ctx context.Context) (*model.Iam, error) {
return repo.IamEvents.IamByID(ctx, repo.IamID)
}

View File

@@ -0,0 +1,75 @@
package eventstore
import (
"context"
"time"
"gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/key/model"
key_event "github.com/caos/zitadel/internal/key/repository/eventsourcing"
)
const (
oidcUser = "OIDC"
iamOrg = "IAM"
)
type KeyRepository struct {
KeyEvents *key_event.KeyEventstore
View *view.View
SigningKeyRotation time.Duration
}
func (k *KeyRepository) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
ctx = setOIDCCtx(ctx)
_, err := k.KeyEvents.GenerateKeyPair(ctx, model.KeyUsageSigning, algorithm)
return err
}
func (k *KeyRepository) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, renewTimer <-chan time.Time) {
go func() {
for {
select {
case <-ctx.Done():
return
case <-renewTimer:
k.refreshSigningKey(keyCh, errCh)
renewTimer = time.After(k.SigningKeyRotation)
}
}
}()
}
func (k *KeyRepository) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error) {
keys, err := k.View.GetActiveKeySet()
if err != nil {
return nil, err
}
webKeys := make([]jose.JSONWebKey, len(keys))
for i, key := range keys {
webKeys[i] = jose.JSONWebKey{KeyID: key.ID, Algorithm: key.Algorithm, Use: key.Usage.String(), Key: key.Key}
}
return &jose.JSONWebKeySet{Keys: webKeys}, nil
}
func (k *KeyRepository) refreshSigningKey(keyCh chan<- jose.SigningKey, errCh chan<- error) {
key, err := k.View.GetSigningKey()
if err != nil {
errCh <- err
return
}
keyCh <- jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(key.Algorithm),
Key: jose.JSONWebKey{
KeyID: key.ID,
Key: key.Key,
},
}
}
func setOIDCCtx(ctx context.Context) context.Context {
return auth.SetCtxData(ctx, auth.CtxData{UserID: oidcUser, OrgID: iamOrg})
}

View File

@@ -0,0 +1,29 @@
package eventstore
import (
"context"
auth_view "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
org_model "github.com/caos/zitadel/internal/org/model"
org_es "github.com/caos/zitadel/internal/org/repository/eventsourcing"
"github.com/caos/zitadel/internal/org/repository/view"
)
type OrgRepository struct {
SearchLimit uint64
*org_es.OrgEventstore
View *auth_view.View
}
func (repo *OrgRepository) SearchOrgs(ctx context.Context, request *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) {
request.EnsureLimit(repo.SearchLimit)
members, count, err := repo.View.SearchOrgs(request)
if err != nil {
return nil, err
}
return &org_model.OrgSearchResult{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: uint64(count),
Result: view.OrgsToModel(members),
}, nil
}

View File

@@ -13,8 +13,8 @@ type TokenRepo struct {
View *view.View
}
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, lifetime time.Duration) (*token_model.Token, error) {
token, err := repo.View.CreateToken(agentID, applicationID, userID, lifetime)
func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID, userID string, audience, scopes []string, lifetime time.Duration) (*token_model.Token, error) {
token, err := repo.View.CreateToken(agentID, applicationID, userID, audience, scopes, lifetime)
if err != nil {
return nil, err
}
@@ -24,3 +24,11 @@ func (repo *TokenRepo) CreateToken(ctx context.Context, agentID, applicationID,
func (repo *TokenRepo) IsTokenValid(ctx context.Context, tokenID string) (bool, error) {
return repo.View.IsTokenValid(tokenID)
}
func (repo *TokenRepo) TokenByID(ctx context.Context, tokenID string) (*token_model.Token, error) {
token, err := repo.View.TokenByID(tokenID)
if err != nil {
return nil, err
}
return token_view_model.TokenToModel(token), nil
}

View File

@@ -56,10 +56,18 @@ func (repo *UserRepo) ChangeMyEmail(ctx context.Context, email *model.Email) (*m
return repo.UserEvents.ChangeEmail(ctx, email)
}
func (repo *UserRepo) VerifyEmail(ctx context.Context, userID, code string) error {
return repo.UserEvents.VerifyEmail(ctx, userID, code)
}
func (repo *UserRepo) VerifyMyEmail(ctx context.Context, code string) error {
return repo.UserEvents.VerifyEmail(ctx, auth.GetCtxData(ctx).UserID, code)
}
func (repo *UserRepo) ResendEmailVerificationMail(ctx context.Context, userID string) error {
return repo.UserEvents.CreateEmailVerificationCode(ctx, userID)
}
func (repo *UserRepo) ResendMyEmailVerificationMail(ctx context.Context) error {
return repo.UserEvents.CreateEmailVerificationCode(ctx, auth.GetCtxData(ctx).UserID)
}
@@ -103,11 +111,28 @@ func (repo *UserRepo) ChangeMyPassword(ctx context.Context, old, new string) err
return err
}
func (repo *UserRepo) ChangePassword(ctx context.Context, userID, old, new string) error {
policy, err := repo.PolicyEvents.GetPasswordComplexityPolicy(ctx, auth.GetCtxData(ctx).OrgID)
if err != nil {
return err
}
_, err = repo.UserEvents.ChangePassword(ctx, policy, userID, old, new)
return err
}
func (repo *UserRepo) AddMfaOTP(ctx context.Context, userID string) (*model.OTP, error) {
return repo.UserEvents.AddOTP(ctx, userID)
}
func (repo *UserRepo) AddMyMfaOTP(ctx context.Context) (*model.OTP, error) {
return repo.UserEvents.AddOTP(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) VerifyMyMfaOTP(ctx context.Context, code string) error {
func (repo *UserRepo) VerifyMfaOTPSetup(ctx context.Context, userID, code string) error {
return repo.UserEvents.CheckMfaOTPSetup(ctx, userID, code)
}
func (repo *UserRepo) VerifyMyMfaOTPSetup(ctx context.Context, code string) error {
return repo.UserEvents.CheckMfaOTPSetup(ctx, auth.GetCtxData(ctx).UserID, code)
}
@@ -115,6 +140,19 @@ func (repo *UserRepo) RemoveMyMfaOTP(ctx context.Context) error {
return repo.UserEvents.RemoveOTP(ctx, auth.GetCtxData(ctx).UserID)
}
func (repo *UserRepo) ResendInitVerificationMail(ctx context.Context, userID string) error {
_, err := repo.UserEvents.CreateInitializeUserCodeByID(ctx, userID)
return err
}
func (repo *UserRepo) VerifyInitCode(ctx context.Context, userID, code, password string) error {
policy, err := repo.PolicyEvents.GetPasswordComplexityPolicy(ctx, auth.GetCtxData(ctx).OrgID)
if err != nil {
return err
}
return repo.UserEvents.VerifyInitCode(ctx, policy, userID, code, password)
}
func (repo *UserRepo) SkipMfaInit(ctx context.Context, userID string) error {
return repo.UserEvents.SkipMfaInit(ctx, userID)
}
@@ -139,6 +177,10 @@ func (repo *UserRepo) SignOut(ctx context.Context, agentID, userID string) error
return repo.UserEvents.SignOut(ctx, agentID, userID)
}
func (repo *UserRepo) UserByID(ctx context.Context, userID string) (*model.User, error) {
return repo.UserEvents.UserByID(ctx, userID)
}
func checkIDs(ctx context.Context, obj es_models.ObjectRoot) error {
if obj.AggregateID != auth.GetCtxData(ctx).UserID {
return errors.ThrowPermissionDenied(nil, "EVENT-kFi9w", "object does not belong to user")

View File

@@ -0,0 +1,158 @@
package eventstore
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
authz_repo "github.com/caos/zitadel/internal/authz/repository/eventsourcing"
caos_errs "github.com/caos/zitadel/internal/errors"
global_model "github.com/caos/zitadel/internal/model"
org_model "github.com/caos/zitadel/internal/org/model"
org_view "github.com/caos/zitadel/internal/org/repository/view"
grant_model "github.com/caos/zitadel/internal/usergrant/model"
"github.com/caos/zitadel/internal/usergrant/repository/view/model"
)
type UserGrantRepo struct {
SearchLimit uint64
View *view.View
IamID string
Auth auth.Config
AuthZRepo *authz_repo.EsRepository
}
func (repo *UserGrantRepo) SearchMyUserGrants(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.UserGrantSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_USER_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: auth.GetCtxData(ctx).UserID})
grants, count, err := repo.View.SearchUserGrants(request)
if err != nil {
return nil, err
}
return &grant_model.UserGrantSearchResponse{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: uint64(count),
Result: model.UserGrantsToModel(grants),
}, nil
}
func (repo *UserGrantRepo) SearchMyProjectOrgs(ctx context.Context, request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
ctxData := auth.GetCtxData(ctx)
if ctxData.ProjectID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "APP-7lqva", "Could not get ProjectID")
}
if ctxData.ProjectID == repo.AuthZRepo.IamProjectID {
isAdmin, err := repo.IsIamAdmin(ctx)
if err != nil {
return nil, err
}
if isAdmin {
return repo.SearchAdminOrgs(request)
}
}
request.Queries = append(request.Queries, &grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_PROJECT_ID, Method: global_model.SEARCHMETHOD_EQUALS, Value: ctxData.ProjectID})
grants, err := repo.SearchMyUserGrants(ctx, request)
if err != nil {
return nil, err
}
return grantRespToOrgResp(grants), nil
}
func (repo *UserGrantRepo) SearchMyZitadelPermissions(ctx context.Context) ([]string, error) {
grant, err := repo.AuthZRepo.ResolveGrants(ctx)
if err != nil {
return nil, err
}
permissions := &grant_model.Permissions{Permissions: []string{}}
for _, role := range grant.Roles {
roleName, ctxID := auth.SplitPermission(role)
for _, mapping := range repo.Auth.RolePermissionMappings {
if mapping.Role == roleName {
permissions.AppendPermissions(ctxID, mapping.Permissions...)
}
}
}
return permissions.Permissions, nil
}
func (repo *UserGrantRepo) SearchAdminOrgs(request *grant_model.UserGrantSearchRequest) (*grant_model.ProjectOrgSearchResponse, error) {
searchRequest := &org_model.OrgSearchRequest{}
if len(request.Queries) > 0 {
for _, q := range request.Queries {
if q.Key == grant_model.USERGRANTSEARCHKEY_ORG_NAME {
searchRequest.Queries = append(searchRequest.Queries, &org_model.OrgSearchQuery{Key: org_model.ORGSEARCHKEY_ORG_NAME, Method: q.Method, Value: q.Value})
}
}
}
orgs, count, err := repo.View.SearchOrgs(searchRequest)
if err != nil {
return nil, err
}
return orgRespToOrgResp(orgs, count), nil
}
func (repo *UserGrantRepo) IsIamAdmin(ctx context.Context) (bool, error) {
grantSearch := &grant_model.UserGrantSearchRequest{
Queries: []*grant_model.UserGrantSearchQuery{
&grant_model.UserGrantSearchQuery{Key: grant_model.USERGRANTSEARCHKEY_RESOURCEOWNER, Method: global_model.SEARCHMETHOD_EQUALS, Value: repo.IamID},
}}
result, err := repo.SearchMyUserGrants(ctx, grantSearch)
if err != nil {
return false, err
}
if result.TotalResult == 0 {
return false, nil
}
return true, nil
}
func grantRespToOrgResp(grants *grant_model.UserGrantSearchResponse) *grant_model.ProjectOrgSearchResponse {
resp := &grant_model.ProjectOrgSearchResponse{
TotalResult: grants.TotalResult,
}
resp.Result = make([]*grant_model.Org, len(grants.Result))
for i, g := range grants.Result {
resp.Result[i] = &grant_model.Org{OrgID: g.ResourceOwner, OrgName: g.OrgName}
}
return resp
}
func orgRespToOrgResp(orgs []*org_view.OrgView, count int) *grant_model.ProjectOrgSearchResponse {
resp := &grant_model.ProjectOrgSearchResponse{
TotalResult: uint64(count),
}
resp.Result = make([]*grant_model.Org, len(orgs))
for i, o := range orgs {
resp.Result[i] = &grant_model.Org{OrgID: o.ID, OrgName: o.Name}
}
return resp
}
func mergeOrgAndAdminGrant(ctxData auth.CtxData, orgGrant, iamAdminGrant *model.UserGrantView) (grant *auth.Grant) {
if orgGrant != nil {
roles := orgGrant.RoleKeys
if iamAdminGrant != nil {
roles = addIamAdminRoles(roles, iamAdminGrant.RoleKeys)
}
grant = &auth.Grant{OrgID: orgGrant.ResourceOwner, Roles: roles}
} else if iamAdminGrant != nil {
grant = &auth.Grant{
OrgID: ctxData.OrgID,
Roles: iamAdminGrant.RoleKeys,
}
}
return grant
}
func addIamAdminRoles(orgRoles, iamAdminRoles []string) []string {
result := make([]string, 0)
result = append(result, iamAdminRoles...)
for _, role := range orgRoles {
if !auth.ExistsPerm(result, role) {
result = append(result, role)
}
}
return result
}

View File

@@ -0,0 +1,21 @@
package eventstore
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
usr_model "github.com/caos/zitadel/internal/user/model"
"github.com/caos/zitadel/internal/user/repository/view/model"
)
type UserSessionRepo struct {
View *view.View
}
func (repo *UserSessionRepo) GetMyUserSessions(ctx context.Context) ([]*usr_model.UserSessionView, error) {
userSessions, err := repo.View.UserSessionsByUserID(auth.GetCtxData(ctx).UserID)
if err != nil {
return nil, err
}
return model.UserSessionsToModel(userSessions), nil
}