diff --git a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html index c4436c0f6d..2a3e7f4117 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html +++ b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html @@ -89,6 +89,9 @@ {{'PROJECT.ROLE.CHECK' | translate}}

{{'PROJECT.ROLE.CHECK_DESCRIPTION' | translate}}

+ + {{'PROJECT.HAS_PROJECT' | translate}} +

{{'PROJECT.HAS_PROJECT_DESCRIPTION' | translate}}

- \ No newline at end of file + diff --git a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts index e763beea8b..e2bf69d7de 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts +++ b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts @@ -185,6 +185,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy { req.setName(this.project.name); req.setProjectRoleAssertion(this.project.projectRoleAssertion); req.setProjectRoleCheck(this.project.projectRoleCheck); + req.setHasProjectCheck(this.project.hasProjectCheck); this.mgmtService.updateProject(req).then(() => { this.toast.showInfo('PROJECT.TOAST.UPDATED', true); diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index ebf9dbcb2c..ed2a6f5015 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1034,6 +1034,8 @@ "CHECK": "Rollen bei Authentisierung prüfen", "CHECK_DESCRIPTION": "Ist das Attribut gesetzt, kann ein Benutzer nur mit einem entsprechenden Rolle authentifiziert werden." }, + "HAS_PROJECT": "Projektbesitz bei Authentisierung prüfen", + "HAS_PROJECT_DESCRIPTION": "Es wird überprüft, ob die Organisation des Benutzers über dieses Projekt verfügt. Wenn nicht, kann der Benutzer nicht authentifiziert werden.", "TABLE": { "TOTAL": "Einträge gesamt:", "SELECTION": "ausgewählt", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 96c6e89283..28056c0871 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1036,6 +1036,8 @@ "CHECK": "Check roles on Authentication", "CHECK_DESCRIPTION": "If set, users are only allowed to authenticate if any role is assigned to their account." }, + "HAS_PROJECT": "Check for Project on Authentication", + "HAS_PROJECT_DESCRIPTION": "It is checked whether the user's organisation has this project. If not, the user cannot be authenticated.", "TABLE": { "TOTAL": "Entries total:", "SELECTION": "Selected Elements", diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md index ae1260fa8c..2d8ffd2633 100644 --- a/docs/docs/apis/proto/management.md +++ b/docs/docs/apis/proto/management.md @@ -3276,6 +3276,7 @@ This is an empty request | name | string | - | string.min_len: 1
string.max_len: 200
| | project_role_assertion | bool | - | | | project_role_check | bool | - | | +| has_project_check | bool | - | | @@ -7520,6 +7521,7 @@ This is an empty request | name | string | - | string.min_len: 1
string.max_len: 200
| | project_role_assertion | bool | - | | | project_role_check | bool | - | | +| has_project_check | bool | - | | diff --git a/docs/docs/apis/proto/project.md b/docs/docs/apis/proto/project.md index ce7ed9c2c8..d925165017 100644 --- a/docs/docs/apis/proto/project.md +++ b/docs/docs/apis/proto/project.md @@ -65,6 +65,7 @@ title: zitadel/project.proto | state | ProjectState | - | | | project_role_assertion | bool | describes if roles of user should be added in token | | | project_role_check | bool | ZITADEL checks if the user has at least one on this project | | +| has_project_check | bool | ZITADEL checks if the org of the user has permission to this project | | diff --git a/docs/docs/apis/proto/text.md b/docs/docs/apis/proto/text.md index a2a0777961..9361d48ee3 100644 --- a/docs/docs/apis/proto/text.md +++ b/docs/docs/apis/proto/text.md @@ -39,6 +39,32 @@ title: zitadel/text.proto +### ExternalRegistrationUserOverviewScreenText + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| title | string | - | string.max_len: 200
| +| description | string | - | string.max_len: 500
| +| email_label | string | - | string.max_len: 200
| +| username_label | string | - | string.max_len: 200
| +| firstname_label | string | - | string.max_len: 200
| +| lastname_label | string | - | string.max_len: 200
| +| nickname_label | string | - | string.max_len: 200
| +| language_label | string | - | string.max_len: 200
| +| phone_label | string | - | string.max_len: 200
| +| tos_and_privacy_label | string | - | string.max_len: 200
| +| tos_confirm | string | - | string.max_len: 200
| +| tos_link_text | string | - | string.max_len: 200
| +| tos_confirm_and | string | - | string.max_len: 200
| +| privacy_link_text | string | - | string.max_len: 200
| +| back_button_text | string | - | string.max_len: 200
| +| next_button_text | string | - | string.max_len: 200
| + + + + ### ExternalUserNotFoundScreenText @@ -49,6 +75,11 @@ title: zitadel/text.proto | description | string | - | string.max_len: 500
| | link_button_text | string | - | string.max_len: 100
| | auto_register_button_text | string | - | string.max_len: 100
| +| tos_and_privacy_label | string | - | string.max_len: 200
| +| tos_confirm | string | - | string.max_len: 200
| +| tos_link_text | string | - | string.max_len: 200
| +| privacy_link_text | string | - | string.max_len: 200
| +| tos_confirm_and | string | - | string.max_len: 200
| @@ -246,6 +277,7 @@ title: zitadel/text.proto | passwordless_prompt_text | PasswordlessPromptScreenText | - | | | passwordless_registration_text | PasswordlessRegistrationScreenText | - | | | passwordless_registration_done_text | PasswordlessRegistrationDoneScreenText | - | | +| external_registration_user_overview_text | ExternalRegistrationUserOverviewScreenText | - | | @@ -405,6 +437,7 @@ title: zitadel/text.proto | title | string | - | string.max_len: 200
| | description | string | - | string.max_len: 500
| | next_button_text | string | - | string.max_len: 100
| +| cancel_button_text | string | - | string.max_len: 100
| diff --git a/internal/api/grpc/management/project_converter.go b/internal/api/grpc/management/project_converter.go index 703d151a15..ba18827009 100644 --- a/internal/api/grpc/management/project_converter.go +++ b/internal/api/grpc/management/project_converter.go @@ -15,6 +15,7 @@ func ProjectCreateToDomain(req *mgmt_pb.AddProjectRequest) *domain.Project { Name: req.Name, ProjectRoleAssertion: req.ProjectRoleAssertion, ProjectRoleCheck: req.ProjectRoleCheck, + HasProjectCheck: req.HasProjectCheck, } } @@ -26,6 +27,7 @@ func ProjectUpdateToDomain(req *mgmt_pb.UpdateProjectRequest) *domain.Project { Name: req.Name, ProjectRoleAssertion: req.ProjectRoleAssertion, ProjectRoleCheck: req.ProjectRoleCheck, + HasProjectCheck: req.HasProjectCheck, } } diff --git a/internal/api/grpc/project/converter.go b/internal/api/grpc/project/converter.go index 1aa3b56845..28420e707b 100644 --- a/internal/api/grpc/project/converter.go +++ b/internal/api/grpc/project/converter.go @@ -15,6 +15,7 @@ func ProjectToPb(project *proj_model.ProjectView) *proj_pb.Project { State: projectStateToPb(project.State), ProjectRoleAssertion: project.ProjectRoleAssertion, ProjectRoleCheck: project.ProjectRoleCheck, + HasProjectCheck: project.HasProjectCheck, } } diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 2148a21c19..f5fa340ace 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -44,6 +44,7 @@ type AuthRequestRepo struct { LockoutPolicyViewProvider lockoutPolicyViewProvider IDPProviderViewProvider idpProviderViewProvider UserGrantProvider userGrantProvider + ProjectProvider projectProvider IdGenerator id.Generator @@ -96,6 +97,11 @@ type userGrantProvider interface { UserGrantsByProjectAndUserID(string, string) ([]*grant_view_model.UserGrantView, error) } +type projectProvider interface { + ApplicationByClientID(context.Context, string) (*project_view_model.ApplicationView, error) + OrgProjectMappingByIDs(orgID, projectID string) (*project_view_model.OrgProjectMapping, error) +} + func (repo *AuthRequestRepo) Health(ctx context.Context) error { return repo.AuthRequests.Health(ctx) } @@ -680,7 +686,15 @@ func (repo *AuthRequestRepo) nextSteps(ctx context.Context, request *domain.Auth } //PLANNED: consent step - missing, err := userGrantRequired(ctx, request, user, repo.UserGrantProvider) + missing, err := projectRequired(ctx, request, repo.ProjectProvider) + if err != nil { + return nil, err + } + if missing { + return append(steps, &domain.ProjectRequiredStep{}), nil + } + + missing, err = userGrantRequired(ctx, request, user, repo.UserGrantProvider) if err != nil { return nil, err } @@ -1081,6 +1095,7 @@ func linkingIDPConfigExistingInAllowedIDPs(linkingUsers []*domain.ExternalUser, } return true } + func userGrantRequired(ctx context.Context, request *domain.AuthRequest, user *user_model.UserView, userGrantProvider userGrantProvider) (_ bool, err error) { var app *project_view_model.ApplicationView switch request.Request.Type() { @@ -1101,3 +1116,27 @@ func userGrantRequired(ctx context.Context, request *domain.AuthRequest, user *u } return len(grants) == 0, nil } + +func projectRequired(ctx context.Context, request *domain.AuthRequest, projectProvider projectProvider) (_ bool, err error) { + var app *project_view_model.ApplicationView + switch request.Request.Type() { + case domain.AuthRequestTypeOIDC: + app, err = projectProvider.ApplicationByClientID(ctx, request.ApplicationID) + if err != nil { + return false, err + } + default: + return false, errors.ThrowPreconditionFailed(nil, "EVENT-dfrw2", "Errors.AuthRequest.RequestTypeNotSupported") + } + if !app.HasProjectCheck { + return false, nil + } + _, err = projectProvider.OrgProjectMappingByIDs(request.UserOrgID, app.ProjectID) + if errors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, err + } + return false, nil +} diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 283502264c..8f4ee22a29 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -6,9 +6,10 @@ import ( "testing" "time" - "github.com/caos/zitadel/internal/crypto" "github.com/stretchr/testify/assert" + "github.com/caos/zitadel/internal/crypto" + "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" @@ -227,6 +228,22 @@ func (m *mockUserGrants) UserGrantsByProjectAndUserID(s string, s2 string) ([]*g return grants, nil } +type mockProject struct { + hasProject bool + projectCheck bool +} + +func (m *mockProject) ApplicationByClientID(ctx context.Context, s string) (*proj_view_model.ApplicationView, error) { + return &proj_view_model.ApplicationView{HasProjectCheck: m.projectCheck}, nil +} + +func (m *mockProject) OrgProjectMappingByIDs(orgID, projectID string) (*proj_view_model.OrgProjectMapping, error) { + if m.hasProject { + return &proj_view_model.OrgProjectMapping{OrgID: orgID, ProjectID: projectID}, nil + } + return nil, errors.ThrowNotFound(nil, "ERROR", "error") +} + func TestAuthRequestRepo_nextSteps(t *testing.T) { type fields struct { AuthRequests *cache.AuthRequestCache @@ -236,6 +253,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider userEventProvider orgViewProvider orgViewProvider userGrantProvider userGrantProvider + projectProvider projectProvider loginPolicyProvider loginPolicyViewProvider lockoutPolicyProvider lockoutPolicyViewProvider PasswordCheckLifeTime time.Duration @@ -684,6 +702,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{}, loginPolicyProvider: &mockLoginPolicy{ policy: &iam_view_model.LoginPolicyView{}, }, @@ -741,6 +760,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -971,6 +991,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -1004,6 +1025,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { userEventProvider: &mockEventUser{}, orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -1041,6 +1063,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { roleCheck: true, userGrants: 0, }, + projectProvider: &mockProject{}, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -1078,6 +1101,83 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { roleCheck: true, userGrants: 2, }, + projectProvider: &mockProject{}, + lockoutPolicyProvider: &mockLockoutPolicy{ + policy: &iam_view_model.LockoutPolicyView{ + ShowLockOutFailures: true, + }, + }, + PasswordCheckLifeTime: 10 * 24 * time.Hour, + SecondFactorCheckLifeTime: 18 * time.Hour, + }, + args{&domain.AuthRequest{ + UserID: "UserID", + Prompt: []domain.Prompt{domain.PromptNone}, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + }, + }, true}, + []domain.NextStep{&domain.RedirectToCallbackStep{}}, + nil, + }, + { + "prompt none, checkLoggedIn true, authenticated and required project missing, project required step", + fields{ + userSessionViewProvider: &mockViewUserSession{ + PasswordVerification: time.Now().UTC().Add(-5 * time.Minute), + SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute), + }, + userViewProvider: &mockViewUser{ + PasswordSet: true, + IsEmailVerified: true, + MFAMaxSetUp: int32(model.MFALevelSecondFactor), + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{ + projectCheck: true, + hasProject: false, + }, + lockoutPolicyProvider: &mockLockoutPolicy{ + policy: &iam_view_model.LockoutPolicyView{ + ShowLockOutFailures: true, + }, + }, + PasswordCheckLifeTime: 10 * 24 * time.Hour, + SecondFactorCheckLifeTime: 18 * time.Hour, + }, + args{&domain.AuthRequest{ + UserID: "UserID", + Prompt: []domain.Prompt{domain.PromptNone}, + Request: &domain.AuthRequestOIDC{}, + LoginPolicy: &domain.LoginPolicy{ + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + }, + }, true}, + []domain.NextStep{&domain.ProjectRequiredStep{}}, + nil, + }, + { + "prompt none, checkLoggedIn true, authenticated and required project exist, redirect to callback step", + fields{ + userSessionViewProvider: &mockViewUserSession{ + PasswordVerification: time.Now().UTC().Add(-5 * time.Minute), + SecondFactorVerification: time.Now().UTC().Add(-5 * time.Minute), + }, + userViewProvider: &mockViewUser{ + PasswordSet: true, + IsEmailVerified: true, + MFAMaxSetUp: int32(model.MFALevelSecondFactor), + }, + userEventProvider: &mockEventUser{}, + orgViewProvider: &mockViewOrg{State: org_model.OrgStateActive}, + userGrantProvider: &mockUserGrants{}, + projectProvider: &mockProject{ + projectCheck: true, + hasProject: true, + }, lockoutPolicyProvider: &mockLockoutPolicy{ policy: &iam_view_model.LockoutPolicyView{ ShowLockOutFailures: true, @@ -1172,6 +1272,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { UserEventProvider: tt.fields.userEventProvider, OrgViewProvider: tt.fields.orgViewProvider, UserGrantProvider: tt.fields.userGrantProvider, + ProjectProvider: tt.fields.projectProvider, LoginPolicyViewProvider: tt.fields.loginPolicyProvider, LockoutPolicyViewProvider: tt.fields.lockoutPolicyProvider, PasswordCheckLifeTime: tt.fields.PasswordCheckLifeTime, diff --git a/internal/auth/repository/eventsourcing/handler/application.go b/internal/auth/repository/eventsourcing/handler/application.go index 7ef1d6fbba..65e75bdf71 100644 --- a/internal/auth/repository/eventsourcing/handler/application.go +++ b/internal/auth/repository/eventsourcing/handler/application.go @@ -82,6 +82,7 @@ func (a *Application) Reduce(event *models.Event) (err error) { return err } app.ProjectRoleCheck = project.ProjectRoleCheck + app.HasProjectCheck = project.HasProjectCheck app.ProjectRoleAssertion = project.ProjectRoleAssertion err = app.AppendEvent(event) diff --git a/internal/auth/repository/eventsourcing/handler/handler.go b/internal/auth/repository/eventsourcing/handler/handler.go index 67a2beae73..5c3daac5d8 100644 --- a/internal/auth/repository/eventsourcing/handler/handler.go +++ b/internal/auth/repository/eventsourcing/handler/handler.go @@ -74,6 +74,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es newCustomText(handler{view, bulkLimit, configs.cycleDuration("CustomTexts"), errorCount, es}), newMetadata(handler{view, bulkLimit, configs.cycleDuration("Metadata"), errorCount, es}), newLockoutPolicy(handler{view, bulkLimit, configs.cycleDuration("LockoutPolicy"), errorCount, es}), + newOrgProjectMapping(handler{view, bulkLimit, configs.cycleDuration("OrgProjectMapping"), errorCount, es}), } } diff --git a/internal/auth/repository/eventsourcing/handler/org_project_mapping.go b/internal/auth/repository/eventsourcing/handler/org_project_mapping.go new file mode 100644 index 0000000000..8c6cb85712 --- /dev/null +++ b/internal/auth/repository/eventsourcing/handler/org_project_mapping.go @@ -0,0 +1,113 @@ +package handler + +import ( + "github.com/caos/logging" + + "github.com/caos/zitadel/internal/eventstore/v1" + es_models "github.com/caos/zitadel/internal/eventstore/v1/models" + "github.com/caos/zitadel/internal/eventstore/v1/query" + "github.com/caos/zitadel/internal/eventstore/v1/spooler" + "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" + proj_view "github.com/caos/zitadel/internal/project/repository/view" + view_model "github.com/caos/zitadel/internal/project/repository/view/model" +) + +const ( + orgProjectMappingTable = "auth.org_project_mapping" +) + +type OrgProjectMapping struct { + handler + subscription *v1.Subscription +} + +func newOrgProjectMapping( + handler handler, +) *OrgProjectMapping { + h := &OrgProjectMapping{ + handler: handler, + } + + h.subscribe() + + return h +} + +func (k *OrgProjectMapping) subscribe() { + k.subscription = k.es.Subscribe(k.AggregateTypes()...) + go func() { + for event := range k.subscription.Events { + query.ReduceEvent(k, event) + } + }() +} + +func (p *OrgProjectMapping) ViewModel() string { + return orgProjectMappingTable +} + +func (p *OrgProjectMapping) Subscription() *v1.Subscription { + return p.subscription +} + +func (_ *OrgProjectMapping) AggregateTypes() []es_models.AggregateType { + return []es_models.AggregateType{model.ProjectAggregate} +} + +func (p *OrgProjectMapping) CurrentSequence() (uint64, error) { + sequence, err := p.view.GetLatestOrgProjectMappingSequence() + if err != nil { + return 0, err + } + return sequence.CurrentSequence, nil +} + +func (p *OrgProjectMapping) EventQuery() (*es_models.SearchQuery, error) { + sequence, err := p.view.GetLatestOrgProjectMappingSequence() + if err != nil { + return nil, err + } + return proj_view.ProjectQuery(sequence.CurrentSequence), nil +} + +func (p *OrgProjectMapping) Reduce(event *es_models.Event) (err error) { + mapping := new(view_model.OrgProjectMapping) + switch event.Type { + case model.ProjectAdded: + mapping.OrgID = event.ResourceOwner + mapping.ProjectID = event.AggregateID + case model.ProjectRemoved: + err := p.view.DeleteOrgProjectMappingsByProjectID(event.AggregateID) + if err == nil { + return p.view.ProcessedOrgProjectMappingSequence(event) + } + case model.ProjectGrantAdded: + projectGrant := new(view_model.ProjectGrant) + projectGrant.SetData(event) + mapping.OrgID = projectGrant.GrantedOrgID + mapping.ProjectID = event.AggregateID + mapping.ProjectGrantID = projectGrant.GrantID + case model.ProjectGrantRemoved: + projectGrant := new(view_model.ProjectGrant) + projectGrant.SetData(event) + err := p.view.DeleteOrgProjectMappingsByProjectGrantID(event.AggregateID) + if err == nil { + return p.view.ProcessedOrgProjectMappingSequence(event) + } + default: + return p.view.ProcessedOrgProjectMappingSequence(event) + } + if err != nil { + return err + } + return p.view.PutOrgProjectMapping(mapping, event) +} + +func (p *OrgProjectMapping) OnError(event *es_models.Event, err error) error { + logging.LogWithFields("SPOOL-2k0fS", "id", event.AggregateID).WithError(err).Warn("something went wrong in org project mapping handler") + return spooler.HandleError(event, err, p.view.GetLatestOrgProjectMappingFailedEvent, p.view.ProcessedOrgProjectMappingFailedEvent, p.view.ProcessedOrgProjectMappingSequence, p.errorCountUntilSkip) +} + +func (p *OrgProjectMapping) OnSuccess() error { + return spooler.HandleSuccess(p.view.UpdateOrgProjectMappingSpoolerRunTimestamp) +} diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 7d4523951d..6df9f147fc 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -111,6 +111,7 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co LoginPolicyViewProvider: view, LockoutPolicyViewProvider: view, UserGrantProvider: view, + ProjectProvider: view, IdGenerator: idGenerator, PasswordCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, ExternalLoginCheckLifeTime: systemDefaults.VerificationLifetimes.PasswordCheck.Duration, diff --git a/internal/auth/repository/eventsourcing/view/org_project_mapping.go b/internal/auth/repository/eventsourcing/view/org_project_mapping.go new file mode 100644 index 0000000000..8103cf8c5c --- /dev/null +++ b/internal/auth/repository/eventsourcing/view/org_project_mapping.go @@ -0,0 +1,61 @@ +package view + +import ( + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v1/models" + "github.com/caos/zitadel/internal/project/repository/view" + "github.com/caos/zitadel/internal/project/repository/view/model" + "github.com/caos/zitadel/internal/view/repository" +) + +const ( + orgPrgojectMappingTable = "auth.org_project_mapping" +) + +func (v *View) OrgProjectMappingByIDs(orgID, projectID string) (*model.OrgProjectMapping, error) { + return view.OrgProjectMappingByIDs(v.Db, orgPrgojectMappingTable, orgID, projectID) +} + +func (v *View) PutOrgProjectMapping(mapping *model.OrgProjectMapping, event *models.Event) error { + err := view.PutOrgProjectMapping(v.Db, orgPrgojectMappingTable, mapping) + if err != nil { + return err + } + return v.ProcessedOrgProjectMappingSequence(event) +} + +func (v *View) DeleteOrgProjectMapping(orgID, projectID string, event *models.Event) error { + err := view.DeleteOrgProjectMapping(v.Db, orgPrgojectMappingTable, orgID, projectID) + if err != nil && !errors.IsNotFound(err) { + return err + } + return v.ProcessedOrgProjectMappingSequence(event) +} + +func (v *View) DeleteOrgProjectMappingsByProjectID(projectID string) error { + return view.DeleteOrgProjectMappingsByProjectID(v.Db, orgPrgojectMappingTable, projectID) +} + +func (v *View) DeleteOrgProjectMappingsByProjectGrantID(projectGrantID string) error { + return view.DeleteOrgProjectMappingsByProjectGrantID(v.Db, orgPrgojectMappingTable, projectGrantID) +} + +func (v *View) GetLatestOrgProjectMappingSequence() (*repository.CurrentSequence, error) { + return v.latestSequence(orgPrgojectMappingTable) +} + +func (v *View) ProcessedOrgProjectMappingSequence(event *models.Event) error { + return v.saveCurrentSequence(orgPrgojectMappingTable, event) +} + +func (v *View) UpdateOrgProjectMappingSpoolerRunTimestamp() error { + return v.updateSpoolerRunSequence(orgPrgojectMappingTable) +} + +func (v *View) GetLatestOrgProjectMappingFailedEvent(sequence uint64) (*repository.FailedEvent, error) { + return v.latestFailedEvent(orgPrgojectMappingTable, sequence) +} + +func (v *View) ProcessedOrgProjectMappingFailedEvent(failedEvent *repository.FailedEvent) error { + return v.saveFailedEvent(failedEvent) +} diff --git a/internal/command/project.go b/internal/command/project.go index b5f3bbdd15..2257d9feb1 100644 --- a/internal/command/project.go +++ b/internal/command/project.go @@ -45,7 +45,7 @@ func (c *Commands) addProject(ctx context.Context, projectAdd *domain.Project, r projectRole = domain.RoleProjectOwnerGlobal } events := []eventstore.EventPusher{ - project.NewProjectAddedEvent(ctx, projectAgg, projectAdd.Name), + project.NewProjectAddedEvent(ctx, projectAgg, projectAdd.Name, projectAdd.ProjectRoleAssertion, projectAdd.ProjectRoleCheck, projectAdd.HasProjectCheck), project.NewProjectMemberAddedEvent(ctx, projectAgg, ownerUserID, projectRole), } return events, addedProject, nil @@ -87,7 +87,13 @@ func (c *Commands) ChangeProject(ctx context.Context, projectChange *domain.Proj } projectAgg := ProjectAggregateFromWriteModel(&existingProject.WriteModel) - changedEvent, hasChanged, err := existingProject.NewChangedEvent(ctx, projectAgg, projectChange.Name, projectChange.ProjectRoleAssertion, projectChange.ProjectRoleCheck) + changedEvent, hasChanged, err := existingProject.NewChangedEvent( + ctx, + projectAgg, + projectChange.Name, + projectChange.ProjectRoleAssertion, + projectChange.ProjectRoleCheck, + projectChange.HasProjectCheck) if err != nil { return nil, err } diff --git a/internal/command/project_application_api_test.go b/internal/command/project_application_api_test.go index a438b4f285..507f0fe249 100644 --- a/internal/command/project_application_api_test.go +++ b/internal/command/project_application_api_test.go @@ -84,7 +84,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), ), @@ -113,7 +113,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( @@ -180,7 +180,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( diff --git a/internal/command/project_application_oidc_test.go b/internal/command/project_application_oidc_test.go index b590cf6cb4..ecfef9ebb0 100644 --- a/internal/command/project_application_oidc_test.go +++ b/internal/command/project_application_oidc_test.go @@ -87,7 +87,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), ), @@ -116,7 +116,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( diff --git a/internal/command/project_converter.go b/internal/command/project_converter.go index 2aee9bb5ce..4af0418c1a 100644 --- a/internal/command/project_converter.go +++ b/internal/command/project_converter.go @@ -10,6 +10,7 @@ func projectWriteModelToProject(writeModel *ProjectWriteModel) *domain.Project { Name: writeModel.Name, ProjectRoleAssertion: writeModel.ProjectRoleAssertion, ProjectRoleCheck: writeModel.ProjectRoleCheck, + HasProjectCheck: writeModel.HasProjectCheck, } } diff --git a/internal/command/project_grant_test.go b/internal/command/project_grant_test.go index 4274622b52..15f28a2cc9 100644 --- a/internal/command/project_grant_test.go +++ b/internal/command/project_grant_test.go @@ -87,7 +87,7 @@ func TestCommandSide_AddProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -116,7 +116,7 @@ func TestCommandSide_AddProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -152,7 +152,7 @@ func TestCommandSide_AddProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -341,7 +341,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -379,7 +379,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -424,7 +424,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -477,7 +477,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -556,7 +556,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -637,7 +637,7 @@ func TestCommandSide_ChangeProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -813,7 +813,7 @@ func TestCommandSide_DeactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -839,7 +839,7 @@ func TestCommandSide_DeactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -876,7 +876,7 @@ func TestCommandSide_DeactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1009,7 +1009,7 @@ func TestCommandSide_ReactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1035,7 +1035,7 @@ func TestCommandSide_ReactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1068,7 +1068,7 @@ func TestCommandSide_ReactivateProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1206,7 +1206,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1232,7 +1232,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1277,7 +1277,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -1324,7 +1324,7 @@ func TestCommandSide_RemoveProjectGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), diff --git a/internal/command/project_model.go b/internal/command/project_model.go index 9e5cc439c6..8655f3d9b8 100644 --- a/internal/command/project_model.go +++ b/internal/command/project_model.go @@ -14,6 +14,7 @@ type ProjectWriteModel struct { Name string ProjectRoleAssertion bool ProjectRoleCheck bool + HasProjectCheck bool State domain.ProjectState } @@ -33,6 +34,7 @@ func (wm *ProjectWriteModel) Reduce() error { wm.Name = e.Name wm.ProjectRoleAssertion = e.ProjectRoleAssertion wm.ProjectRoleCheck = e.ProjectRoleCheck + wm.HasProjectCheck = e.HasProjectCheck wm.State = domain.ProjectStateActive case *project.ProjectChangeEvent: if e.Name != nil { @@ -44,6 +46,9 @@ func (wm *ProjectWriteModel) Reduce() error { if e.ProjectRoleCheck != nil { wm.ProjectRoleCheck = *e.ProjectRoleCheck } + if e.HasProjectCheck != nil { + wm.HasProjectCheck = *e.HasProjectCheck + } case *project.ProjectDeactivatedEvent: if wm.State == domain.ProjectStateRemoved { continue @@ -80,7 +85,8 @@ func (wm *ProjectWriteModel) NewChangedEvent( aggregate *eventstore.Aggregate, name string, projectRoleAssertion, - projectRoleCheck bool, + projectRoleCheck, + hasProjectCheck bool, ) (*project.ProjectChangeEvent, bool, error) { changes := make([]project.ProjectChanges, 0) var err error @@ -96,6 +102,9 @@ func (wm *ProjectWriteModel) NewChangedEvent( if wm.ProjectRoleCheck != projectRoleCheck { changes = append(changes, project.ChangeProjectRoleCheck(projectRoleCheck)) } + if wm.HasProjectCheck != hasProjectCheck { + changes = append(changes, project.ChangeHasProjectCheck(hasProjectCheck)) + } if len(changes) == 0 { return nil, false, nil } diff --git a/internal/command/project_role_test.go b/internal/command/project_role_test.go index a4bebdd1ea..80a13c8577 100644 --- a/internal/command/project_role_test.go +++ b/internal/command/project_role_test.go @@ -41,7 +41,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -76,7 +76,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -104,7 +104,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -148,7 +148,7 @@ func TestCommandSide_AddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -240,7 +240,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -277,7 +277,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -309,7 +309,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -367,7 +367,7 @@ func TestCommandSide_BulkAddProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -486,7 +486,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -521,7 +521,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -568,7 +568,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -609,7 +609,7 @@ func TestCommandSide_ChangeProjectRole(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), diff --git a/internal/command/project_test.go b/internal/command/project_test.go index bfb1afeb55..63d99d8251 100644 --- a/internal/command/project_test.go +++ b/internal/command/project_test.go @@ -2,6 +2,10 @@ package command import ( "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/caos/zitadel/internal/domain" caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" @@ -12,8 +16,6 @@ import ( "github.com/caos/zitadel/internal/repository/iam" "github.com/caos/zitadel/internal/repository/member" "github.com/caos/zitadel/internal/repository/project" - "github.com/stretchr/testify/assert" - "testing" ) func TestCommandSide_AddProject(t *testing.T) { @@ -71,7 +73,7 @@ func TestCommandSide_AddProject(t *testing.T) { eventFromEventPusher(project.NewProjectAddedEvent( context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project", + "project", true, true, true, ), ), eventFromEventPusher(project.NewProjectMemberAddedEvent( @@ -91,7 +93,10 @@ func TestCommandSide_AddProject(t *testing.T) { args: args{ ctx: context.Background(), project: &domain.Project{ - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, resourceOwner: "org1", ownerID: "user1", @@ -118,7 +123,7 @@ func TestCommandSide_AddProject(t *testing.T) { eventFromEventPusher(project.NewProjectAddedEvent( context.Background(), &project.NewAggregate("project1", "globalorg").Aggregate, - "project", + "project", true, true, true, ), ), eventFromEventPusher(project.NewProjectMemberAddedEvent( @@ -138,7 +143,10 @@ func TestCommandSide_AddProject(t *testing.T) { args: args{ ctx: context.Background(), project: &domain.Project{ - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, resourceOwner: "globalorg", ownerID: "user1", @@ -149,7 +157,10 @@ func TestCommandSide_AddProject(t *testing.T) { ResourceOwner: "globalorg", AggregateID: "project1", }, - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, }, }, @@ -171,7 +182,7 @@ func TestCommandSide_AddProject(t *testing.T) { eventFromEventPusher(project.NewProjectAddedEvent( context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project", + "project", true, true, true, ), ), eventFromEventPusher(project.NewProjectMemberAddedEvent( @@ -191,7 +202,10 @@ func TestCommandSide_AddProject(t *testing.T) { args: args{ ctx: context.Background(), project: &domain.Project{ - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, resourceOwner: "org1", ownerID: "user1", @@ -202,7 +216,10 @@ func TestCommandSide_AddProject(t *testing.T) { ResourceOwner: "org1", AggregateID: "project1", }, - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, }, }, @@ -315,7 +332,7 @@ func TestCommandSide_ChangeProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectRemovedEvent(context.Background(), @@ -348,7 +365,7 @@ func TestCommandSide_ChangeProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), ), @@ -359,7 +376,10 @@ func TestCommandSide_ChangeProject(t *testing.T) { ObjectRoot: models.ObjectRoot{ AggregateID: "project1", }, - Name: "project", + Name: "project", + ProjectRoleAssertion: true, + ProjectRoleCheck: true, + HasProjectCheck: true, }, resourceOwner: "org1", }, @@ -376,7 +396,7 @@ func TestCommandSide_ChangeProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( @@ -387,8 +407,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { "org1", "project", "project-new", - true, - true), + false, + false, + false), ), }, uniqueConstraintsFromEventConstraint(project.NewRemoveProjectNameUniqueConstraint("project", "org1")), @@ -403,8 +424,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { AggregateID: "project1", }, Name: "project-new", - ProjectRoleAssertion: true, - ProjectRoleCheck: true, + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, }, resourceOwner: "org1", }, @@ -415,8 +437,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { ResourceOwner: "org1", }, Name: "project-new", - ProjectRoleAssertion: true, - ProjectRoleCheck: true, + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, }, }, }, @@ -429,7 +452,7 @@ func TestCommandSide_ChangeProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( @@ -440,8 +463,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { "org1", "project", "", - true, - true), + false, + false, + false), ), }, ), @@ -454,8 +478,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { AggregateID: "project1", }, Name: "project", - ProjectRoleAssertion: true, - ProjectRoleCheck: true, + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, }, resourceOwner: "org1", }, @@ -466,8 +491,9 @@ func TestCommandSide_ChangeProject(t *testing.T) { ResourceOwner: "org1", }, Name: "project", - ProjectRoleAssertion: true, - ProjectRoleCheck: true, + ProjectRoleAssertion: false, + ProjectRoleCheck: false, + HasProjectCheck: false, }, }, }, @@ -568,7 +594,7 @@ func TestCommandSide_DeactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectRemovedEvent(context.Background(), @@ -596,7 +622,7 @@ func TestCommandSide_DeactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectDeactivatedEvent(context.Background(), @@ -623,7 +649,7 @@ func TestCommandSide_DeactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( @@ -744,7 +770,7 @@ func TestCommandSide_ReactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectRemovedEvent(context.Background(), @@ -772,7 +798,7 @@ func TestCommandSide_ReactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), ), @@ -795,7 +821,7 @@ func TestCommandSide_ReactivateProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectDeactivatedEvent(context.Background(), @@ -920,7 +946,7 @@ func TestCommandSide_RemoveProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), eventFromEventPusher( project.NewProjectRemovedEvent(context.Background(), @@ -948,7 +974,7 @@ func TestCommandSide_RemoveProject(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "project"), + "project", true, true, true), ), ), expectPush( @@ -994,10 +1020,11 @@ func TestCommandSide_RemoveProject(t *testing.T) { } } -func newProjectChangedEvent(ctx context.Context, projectID, resourceOwner, oldName, newName string, roleAssertion, roleCheck bool) *project.ProjectChangeEvent { +func newProjectChangedEvent(ctx context.Context, projectID, resourceOwner, oldName, newName string, roleAssertion, roleCheck, hasProjectCheck bool) *project.ProjectChangeEvent { changes := []project.ProjectChanges{ project.ChangeProjectRoleAssertion(roleAssertion), project.ChangeProjectRoleCheck(roleCheck), + project.ChangeHasProjectCheck(hasProjectCheck), } if newName != "" { changes = append(changes, project.ChangeName(newName)) diff --git a/internal/command/user_grant_test.go b/internal/command/user_grant_test.go index 5a8f57e11d..a3ce1a7a2b 100644 --- a/internal/command/user_grant_test.go +++ b/internal/command/user_grant_test.go @@ -140,7 +140,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -187,7 +187,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -229,7 +229,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -272,7 +272,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -331,7 +331,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -403,7 +403,7 @@ func TestCommandSide_AddUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -717,7 +717,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -776,7 +776,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -830,7 +830,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), ), @@ -885,7 +885,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -956,7 +956,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( @@ -1043,7 +1043,7 @@ func TestCommandSide_ChangeUserGrant(t *testing.T) { eventFromEventPusher( project.NewProjectAddedEvent(context.Background(), &project.NewAggregate("project1", "org1").Aggregate, - "projectname1", + "projectname1", true, true, true, ), ), eventFromEventPusher( diff --git a/internal/domain/next_step.go b/internal/domain/next_step.go index 1d5c201f0d..57eaa3304b 100644 --- a/internal/domain/next_step.go +++ b/internal/domain/next_step.go @@ -26,6 +26,7 @@ const ( NextStepPasswordless NextStepPasswordlessRegistrationPrompt NextStepRegistration + NextStepProjectRequired ) type LoginStep struct{} @@ -161,6 +162,12 @@ func (s *GrantRequiredStep) Type() NextStepType { return NextStepGrantRequired } +type ProjectRequiredStep struct{} + +func (s *ProjectRequiredStep) Type() NextStepType { + return NextStepProjectRequired +} + type RedirectToCallbackStep struct{} func (s *RedirectToCallbackStep) Type() NextStepType { diff --git a/internal/domain/project.go b/internal/domain/project.go index ec83818f5e..fb8262ebbc 100644 --- a/internal/domain/project.go +++ b/internal/domain/project.go @@ -11,6 +11,7 @@ type Project struct { Name string ProjectRoleAssertion bool ProjectRoleCheck bool + HasProjectCheck bool } type ProjectState int32 diff --git a/internal/management/repository/eventsourcing/handler/application.go b/internal/management/repository/eventsourcing/handler/application.go index c7be7ecffe..dd45a94586 100644 --- a/internal/management/repository/eventsourcing/handler/application.go +++ b/internal/management/repository/eventsourcing/handler/application.go @@ -84,6 +84,7 @@ func (a *Application) Reduce(event *models.Event) (err error) { return err } app.ProjectRoleCheck = project.ProjectRoleCheck + app.HasProjectCheck = project.HasProjectCheck app.ProjectRoleAssertion = project.ProjectRoleAssertion err = app.AppendEvent(event) diff --git a/internal/project/model/org_project_mapping_view.go b/internal/project/model/org_project_mapping_view.go new file mode 100644 index 0000000000..3b71510666 --- /dev/null +++ b/internal/project/model/org_project_mapping_view.go @@ -0,0 +1,53 @@ +package model + +import ( + "github.com/caos/zitadel/internal/domain" + + "time" +) + +type OrgProjectMapping struct { + OrgID string + ProjectID string +} + +type OrgProjectMappingViewSearchRequest struct { + Offset uint64 + Limit uint64 + SortingColumn OrgProjectMappingViewSearchKey + Asc bool + Queries []*OrgProjectMappingViewSearchQuery +} + +type OrgProjectMappingViewSearchKey int32 + +const ( + OrgProjectMappingSearchKeyUnspecified OrgProjectMappingViewSearchKey = iota + OrgProjectMappingSearchKeyProjectID + OrgProjectMappingSearchKeyOrgID + OrgProjectMappingSearchKeyProjectGrantID +) + +type OrgProjectMappingViewSearchQuery struct { + Key OrgProjectMappingViewSearchKey + Method domain.SearchMethod + Value interface{} +} + +type OrgProjectMappingViewSearchResponse struct { + Offset uint64 + Limit uint64 + TotalResult uint64 + Result []*OrgProjectMapping + Sequence uint64 + Timestamp time.Time +} + +func (r *OrgProjectMappingViewSearchRequest) GetSearchQuery(key OrgProjectMappingViewSearchKey) (int, *OrgProjectMappingViewSearchQuery) { + for i, q := range r.Queries { + if q.Key == key { + return i, q + } + } + return -1, nil +} diff --git a/internal/project/model/project.go b/internal/project/model/project.go index 8dd899300f..3a6e3e95f9 100644 --- a/internal/project/model/project.go +++ b/internal/project/model/project.go @@ -17,6 +17,7 @@ type Project struct { Grants []*ProjectGrant ProjectRoleAssertion bool ProjectRoleCheck bool + HasProjectCheck bool } type ProjectChanges struct { Changes []*ProjectChange diff --git a/internal/project/model/project_view.go b/internal/project/model/project_view.go index 87273bb56b..e1f2f52360 100644 --- a/internal/project/model/project_view.go +++ b/internal/project/model/project_view.go @@ -16,6 +16,7 @@ type ProjectView struct { ResourceOwner string ProjectRoleAssertion bool ProjectRoleCheck bool + HasProjectCheck bool Sequence uint64 } diff --git a/internal/project/repository/eventsourcing/model/project.go b/internal/project/repository/eventsourcing/model/project.go index 6eb1b6bb2e..80f8e611fd 100644 --- a/internal/project/repository/eventsourcing/model/project.go +++ b/internal/project/repository/eventsourcing/model/project.go @@ -18,6 +18,7 @@ type Project struct { Name string `json:"name,omitempty"` ProjectRoleAssertion bool `json:"projectRoleAssertion,omitempty"` ProjectRoleCheck bool `json:"projectRoleCheck,omitempty"` + HasProjectCheck bool `json:"hasProjectCheck,omitempty"` State int32 `json:"-"` Members []*ProjectMember `json:"-"` Roles []*ProjectRole `json:"-"` @@ -25,47 +26,6 @@ type Project struct { Grants []*ProjectGrant `json:"-"` } -func GetProject(projects []*Project, id string) (int, *Project) { - for i, p := range projects { - if p.AggregateID == id { - return i, p - } - } - return -1, nil -} - -func (p *Project) Changes(changed *Project) map[string]interface{} { - changes := make(map[string]interface{}, 1) - if changed.Name != "" && p.Name != changed.Name { - changes["name"] = changed.Name - } - if p.ProjectRoleAssertion != changed.ProjectRoleAssertion { - changes["projectRoleAssertion"] = changed.ProjectRoleAssertion - } - if p.ProjectRoleCheck != changed.ProjectRoleCheck { - changes["projectRoleCheck"] = changed.ProjectRoleCheck - } - return changes -} - -func ProjectFromModel(project *model.Project) *Project { - members := ProjectMembersFromModel(project.Members) - roles := ProjectRolesFromModel(project.Roles) - apps := AppsFromModel(project.Applications) - grants := GrantsFromModel(project.Grants) - return &Project{ - ObjectRoot: project.ObjectRoot, - Name: project.Name, - ProjectRoleAssertion: project.ProjectRoleAssertion, - ProjectRoleCheck: project.ProjectRoleCheck, - State: int32(project.State), - Members: members, - Roles: roles, - Applications: apps, - Grants: grants, - } -} - func ProjectToModel(project *Project) *model.Project { members := ProjectMembersToModel(project.Members) roles := ProjectRolesToModel(project.Roles) diff --git a/internal/project/repository/eventsourcing/model/project_test.go b/internal/project/repository/eventsourcing/model/project_test.go index 5da6f8e9f9..6121d63665 100644 --- a/internal/project/repository/eventsourcing/model/project_test.go +++ b/internal/project/repository/eventsourcing/model/project_test.go @@ -8,50 +8,6 @@ import ( "github.com/caos/zitadel/internal/project/model" ) -func TestProjectChanges(t *testing.T) { - type args struct { - existingProject *Project - newProject *Project - } - type res struct { - changesLen int - } - tests := []struct { - name string - args args - res res - }{ - { - name: "project name changes", - args: args{ - existingProject: &Project{Name: "Name"}, - newProject: &Project{Name: "NameChanged"}, - }, - res: res{ - changesLen: 1, - }, - }, - { - name: "no changes", - args: args{ - existingProject: &Project{Name: "Name"}, - newProject: &Project{Name: "Name"}, - }, - res: res{ - changesLen: 0, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - changes := tt.args.existingProject.Changes(tt.args.newProject) - if len(changes) != tt.res.changesLen { - t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes)) - } - }) - } -} - func TestProjectFromEvents(t *testing.T) { type args struct { event []*es_models.Event diff --git a/internal/project/repository/view/model/application.go b/internal/project/repository/view/model/application.go index 4440c10ec1..4ca3473ead 100644 --- a/internal/project/repository/view/model/application.go +++ b/internal/project/repository/view/model/application.go @@ -31,6 +31,7 @@ type ApplicationView struct { State int32 `json:"-" gorm:"column:app_state"` ProjectRoleAssertion bool `json:"projectRoleAssertion" gorm:"column:project_role_assertion"` ProjectRoleCheck bool `json:"projectRoleCheck" gorm:"column:project_role_check"` + HasProjectCheck bool `json:"hasProjectCheck" gorm:"column:has_project_check"` IsOIDC bool `json:"-" gorm:"column:is_oidc"` OIDCVersion int32 `json:"oidcVersion" gorm:"column:oidc_version"` @@ -234,6 +235,7 @@ func (a *ApplicationView) setProjectChanges(event *models.Event) error { changes := struct { ProjectRoleAssertion *bool `json:"projectRoleAssertion,omitempty"` ProjectRoleCheck *bool `json:"projectRoleCheck,omitempty"` + HasProjectCheck *bool `json:"hasProjectCheck,omitempty"` }{} if err := json.Unmarshal(event.Data, &changes); err != nil { logging.Log("EVEN-DFbfg").WithError(err).Error("could not unmarshal event data") @@ -245,5 +247,8 @@ func (a *ApplicationView) setProjectChanges(event *models.Event) error { if changes.ProjectRoleCheck != nil { a.ProjectRoleCheck = *changes.ProjectRoleCheck } + if changes.HasProjectCheck != nil { + a.HasProjectCheck = *changes.HasProjectCheck + } return nil } diff --git a/internal/project/repository/view/model/org_project_mapping.go b/internal/project/repository/view/model/org_project_mapping.go new file mode 100644 index 0000000000..e2f5705d38 --- /dev/null +++ b/internal/project/repository/view/model/org_project_mapping.go @@ -0,0 +1,13 @@ +package model + +const ( + OrgProjectMappingKeyProjectID = "project_id" + OrgProjectMappingKeyOrgID = "org_id" + OrgProjectMappingKeyProjectGrantID = "project_grant_id" +) + +type OrgProjectMapping struct { + ProjectID string `json:"-" gorm:"column:project_id;primary_key"` + OrgID string `json:"-" gorm:"column:org_id;primary_key"` + ProjectGrantID string `json:"-" gorm:"column:project_grant_id;"` +} diff --git a/internal/project/repository/view/model/org_project_mapping_query.go b/internal/project/repository/view/model/org_project_mapping_query.go new file mode 100644 index 0000000000..cc638fcb5c --- /dev/null +++ b/internal/project/repository/view/model/org_project_mapping_query.go @@ -0,0 +1,63 @@ +package model + +import ( + "github.com/caos/zitadel/internal/domain" + proj_model "github.com/caos/zitadel/internal/project/model" + "github.com/caos/zitadel/internal/view/repository" +) + +type OrgProjectMappingSearchRequest proj_model.OrgProjectMappingViewSearchRequest +type OrgProjectMappingSearchQuery proj_model.OrgProjectMappingViewSearchQuery +type OrgProjectMappingSearchKey proj_model.OrgProjectMappingViewSearchKey + +func (req OrgProjectMappingSearchRequest) GetLimit() uint64 { + return req.Limit +} + +func (req OrgProjectMappingSearchRequest) GetOffset() uint64 { + return req.Offset +} + +func (req OrgProjectMappingSearchRequest) GetSortingColumn() repository.ColumnKey { + if req.SortingColumn == proj_model.OrgProjectMappingSearchKeyUnspecified { + return nil + } + return OrgProjectMappingSearchKey(req.SortingColumn) +} + +func (req OrgProjectMappingSearchRequest) GetAsc() bool { + return req.Asc +} + +func (req OrgProjectMappingSearchRequest) GetQueries() []repository.SearchQuery { + result := make([]repository.SearchQuery, len(req.Queries)) + for i, q := range req.Queries { + result[i] = OrgProjectMappingSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method} + } + return result +} + +func (req OrgProjectMappingSearchQuery) GetKey() repository.ColumnKey { + return OrgProjectMappingSearchKey(req.Key) +} + +func (req OrgProjectMappingSearchQuery) GetMethod() domain.SearchMethod { + return req.Method +} + +func (req OrgProjectMappingSearchQuery) GetValue() interface{} { + return req.Value +} + +func (key OrgProjectMappingSearchKey) ToColumnName() string { + switch proj_model.OrgProjectMappingViewSearchKey(key) { + case proj_model.OrgProjectMappingSearchKeyOrgID: + return OrgProjectMappingKeyOrgID + case proj_model.OrgProjectMappingSearchKeyProjectID: + return OrgProjectMappingKeyProjectID + case proj_model.OrgProjectMappingSearchKeyProjectGrantID: + return OrgProjectMappingKeyProjectGrantID + default: + return "" + } +} diff --git a/internal/project/repository/view/model/project.go b/internal/project/repository/view/model/project.go index 0ec8c84665..81a62c7026 100644 --- a/internal/project/repository/view/model/project.go +++ b/internal/project/repository/view/model/project.go @@ -27,23 +27,10 @@ type ProjectView struct { ResourceOwner string `json:"-" gorm:"column:resource_owner"` ProjectRoleAssertion bool `json:"projectRoleAssertion" gorm:"column:project_role_assertion"` ProjectRoleCheck bool `json:"projectRoleCheck" gorm:"column:project_role_check"` + HasProjectCheck bool `json:"hasProjectCheck" gorm:"column:has_project_check"` Sequence uint64 `json:"-" gorm:"column:sequence"` } -func ProjectFromModel(project *model.ProjectView) *ProjectView { - return &ProjectView{ - ProjectID: project.ProjectID, - Name: project.Name, - ChangeDate: project.ChangeDate, - CreationDate: project.CreationDate, - State: int32(project.State), - ResourceOwner: project.ResourceOwner, - ProjectRoleAssertion: project.ProjectRoleAssertion, - ProjectRoleCheck: project.ProjectRoleCheck, - Sequence: project.Sequence, - } -} - func ProjectToModel(project *ProjectView) *model.ProjectView { return &model.ProjectView{ ProjectID: project.ProjectID, @@ -54,6 +41,7 @@ func ProjectToModel(project *ProjectView) *model.ProjectView { ResourceOwner: project.ResourceOwner, ProjectRoleAssertion: project.ProjectRoleAssertion, ProjectRoleCheck: project.ProjectRoleCheck, + HasProjectCheck: project.HasProjectCheck, Sequence: project.Sequence, } } diff --git a/internal/project/repository/view/org_project_mapping_view.go b/internal/project/repository/view/org_project_mapping_view.go new file mode 100644 index 0000000000..ce437daf0e --- /dev/null +++ b/internal/project/repository/view/org_project_mapping_view.go @@ -0,0 +1,51 @@ +package view + +import ( + "github.com/jinzhu/gorm" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + proj_model "github.com/caos/zitadel/internal/project/model" + "github.com/caos/zitadel/internal/project/repository/view/model" + "github.com/caos/zitadel/internal/view/repository" +) + +func OrgProjectMappingByIDs(db *gorm.DB, table, orgID, projectID string) (*model.OrgProjectMapping, error) { + orgProjectMapping := new(model.OrgProjectMapping) + + projectIDQuery := model.OrgProjectMappingSearchQuery{Key: proj_model.OrgProjectMappingSearchKeyProjectID, Value: projectID, Method: domain.SearchMethodEquals} + orgIDQuery := model.OrgProjectMappingSearchQuery{Key: proj_model.OrgProjectMappingSearchKeyOrgID, Value: orgID, Method: domain.SearchMethodEquals} + query := repository.PrepareGetByQuery(table, projectIDQuery, orgIDQuery) + err := query(db, orgProjectMapping) + if caos_errs.IsNotFound(err) { + return nil, caos_errs.ThrowNotFound(nil, "VIEW-fn9fs", "Errors.OrgProjectMapping.NotExisting") + } + return orgProjectMapping, err +} + +func PutOrgProjectMapping(db *gorm.DB, table string, grant *model.OrgProjectMapping) error { + save := repository.PrepareSave(table) + return save(db, grant) +} + +func DeleteOrgProjectMapping(db *gorm.DB, table, orgID, projectID string) error { + projectIDSearch := repository.Key{Key: model.OrgProjectMappingSearchKey(proj_model.OrgProjectMappingSearchKeyProjectID), Value: projectID} + orgIDSearch := repository.Key{Key: model.OrgProjectMappingSearchKey(proj_model.OrgProjectMappingSearchKeyOrgID), Value: orgID} + delete := repository.PrepareDeleteByKeys(table, projectIDSearch, orgIDSearch) + return delete(db) +} + +func DeleteOrgProjectMappingsByProjectID(db *gorm.DB, table, projectID string) error { + delete := repository.PrepareDeleteByKey(table, model.OrgProjectMappingSearchKey(proj_model.OrgProjectMappingSearchKeyProjectID), projectID) + return delete(db) +} + +func DeleteOrgProjectMappingsByProjectGrantID(db *gorm.DB, table, projectGrantID string) error { + delete := repository.PrepareDeleteByKey(table, model.OrgProjectMappingSearchKey(proj_model.OrgProjectMappingSearchKeyProjectGrantID), projectGrantID) + return delete(db) +} + +func DeleteOrgProjectMappingsByOrgID(db *gorm.DB, table, orgID string) error { + delete := repository.PrepareDeleteByKey(table, model.OrgProjectMappingSearchKey(proj_model.OrgProjectMappingSearchKeyOrgID), orgID) + return delete(db) +} diff --git a/internal/repository/project/project.go b/internal/repository/project/project.go index faba643146..c63c0d06e9 100644 --- a/internal/repository/project/project.go +++ b/internal/repository/project/project.go @@ -38,6 +38,7 @@ type ProjectAddedEvent struct { Name string `json:"name,omitempty"` ProjectRoleAssertion bool `json:"projectRoleAssertion,omitempty"` ProjectRoleCheck bool `json:"projectRoleCheck,omitempty"` + HasProjectCheck bool `json:"hasProjectCheck,omitempty"` } func (e *ProjectAddedEvent) Data() interface{} { @@ -52,6 +53,9 @@ func NewProjectAddedEvent( ctx context.Context, aggregate *eventstore.Aggregate, name string, + projectRoleAssertion, + projectRoleCheck, + hasProjectCheck bool, ) *ProjectAddedEvent { return &ProjectAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -59,7 +63,10 @@ func NewProjectAddedEvent( aggregate, ProjectAddedType, ), - Name: name, + Name: name, + ProjectRoleAssertion: projectRoleAssertion, + ProjectRoleCheck: projectRoleCheck, + HasProjectCheck: hasProjectCheck, } } @@ -82,6 +89,7 @@ type ProjectChangeEvent struct { Name *string `json:"name,omitempty"` ProjectRoleAssertion *bool `json:"projectRoleAssertion,omitempty"` ProjectRoleCheck *bool `json:"projectRoleCheck,omitempty"` + HasProjectCheck *bool `json:"hasProjectCheck,omitempty"` oldName string } @@ -142,6 +150,12 @@ func ChangeProjectRoleCheck(projectRoleCheck bool) func(event *ProjectChangeEven } } +func ChangeHasProjectCheck(ChangeHasProjectCheck bool) func(event *ProjectChangeEvent) { + return func(e *ProjectChangeEvent) { + e.HasProjectCheck = &ChangeHasProjectCheck + } +} + func ProjectChangeEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &ProjectChangeEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/internal/ui/login/handler/renderer.go b/internal/ui/login/handler/renderer.go index a715358967..4d3b1d9cbf 100644 --- a/internal/ui/login/handler/renderer.go +++ b/internal/ui/login/handler/renderer.go @@ -291,6 +291,8 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * l.handleExternalLoginStep(w, r, authReq, step.SelectedIDPConfigID) case *domain.GrantRequiredStep: l.renderInternalError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-asb43", "Errors.User.GrantRequired")) + case *domain.ProjectRequiredStep: + l.renderInternalError(w, r, authReq, caos_errs.ThrowPreconditionFailed(nil, "APP-m92d", "Errors.User.ProjectRequired")) default: l.renderInternalError(w, r, authReq, caos_errs.ThrowInternal(nil, "APP-ds3QF", "step no possible")) } diff --git a/internal/ui/login/static/i18n/de.yaml b/internal/ui/login/static/i18n/de.yaml index 0420038a61..c62bd91a31 100644 --- a/internal/ui/login/static/i18n/de.yaml +++ b/internal/ui/login/static/i18n/de.yaml @@ -349,6 +349,7 @@ Errors: ExternalUserIDEmpty: Externe User ID ist leer UserDisplayNameEmpty: Benutzer Anzeige Name ist leer GrantRequired: Der Login an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte melde dich bei deinem Administrator. + ProjectRequired: Der Login an diese Applikation ist nicht möglich. Die Organisation des Benutzer benötigt Berechtigung auf das Projekt. Bitte melde dich bei deinem Administrator. IdentityProvider: InvalidConfig: Identitäts Provider Konfiguration ist ungültig IAM: diff --git a/internal/ui/login/static/i18n/en.yaml b/internal/ui/login/static/i18n/en.yaml index c5e4070bcc..6a3b4256a7 100644 --- a/internal/ui/login/static/i18n/en.yaml +++ b/internal/ui/login/static/i18n/en.yaml @@ -350,6 +350,7 @@ Errors: ExternalUserIDEmpty: External User ID is empty UserDisplayNameEmpty: User Display Name is empty GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator. + ProjectRequired: Login not possible. The organisation of the user must be granted to the project. Please contact your administrator. IdentityProvider: InvalidConfig: Identity Provider configuration is invalid IAM: diff --git a/migrations/cockroach/V1.61__has_project.sql b/migrations/cockroach/V1.61__has_project.sql new file mode 100644 index 0000000000..056d90933d --- /dev/null +++ b/migrations/cockroach/V1.61__has_project.sql @@ -0,0 +1,13 @@ +ALTER TABLE management.projects ADD COLUMN has_project_check BOOLEAN; + +ALTER TABLE authz.applications ADD COLUMN has_project_check BOOLEAN; +ALTER TABLE auth.applications ADD COLUMN has_project_check BOOLEAN; +ALTER TABLE management.applications ADD COLUMN has_project_check BOOLEAN; + +CREATE TABLE auth.org_project_mapping ( + org_id TEXT, + project_id TEXT, + project_grant_id TEXT, + + PRIMARY KEY (org_id, project_id) +); \ No newline at end of file diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index c6758ffe3b..0ced5cc625 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -3452,6 +3452,7 @@ message AddProjectRequest { string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; bool project_role_assertion = 2; bool project_role_check = 3; + bool has_project_check = 4; } message AddProjectResponse { @@ -3464,6 +3465,7 @@ message UpdateProjectRequest { string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}]; bool project_role_assertion = 3; bool project_role_check = 4; + bool has_project_check = 5; } message UpdateProjectResponse { diff --git a/proto/zitadel/project.proto b/proto/zitadel/project.proto index ecda3a2a62..01222a1c23 100644 --- a/proto/zitadel/project.proto +++ b/proto/zitadel/project.proto @@ -29,6 +29,8 @@ message Project { bool project_role_assertion = 5; // ZITADEL checks if the user has at least one on this project bool project_role_check = 6; + // ZITADEL checks if the org of the user has permission to this project + bool has_project_check = 7; } message GrantedProject {