zitadel/internal/command/project_grant.go
Stefan Benz bc9a85daf3
feat: V2 alpha import and export of organizations (#3798)
* feat(import): add functionality to import data into an instance

* feat(import): move import to admin api and additional checks for nil pointer

* fix(export): export implementation with filtered members and grants

* fix: export and import implementation

* fix: add possibility to export hashed passwords with the user

* fix(import): import with structure of v1 and v2

* docs: add v1 proto

* fix(import): check im imported user is already existing

* fix(import): add otp import function

* fix(import): add external idps, domains, custom text and messages

* fix(import): correct usage of default values from login policy

* fix(export): fix renaming of add project function

* fix(import): move checks for unit tests

* expect filter

* fix(import): move checks for unit tests

* fix(import): move checks for unit tests

* fix(import): produce prerelease from branch

* fix(import): correctly use provided user id for machine user imports

* fix(import): corrected otp import and added guide for export and import

* fix: import verified and primary domains

* fix(import): add reading from gcs, s3 and localfile with tracing

* fix(import): gcs and s3, file size correction and error logging

* Delete docker-compose.yml

* fix(import): progress logging and count of resources

* fix(import): progress logging and count of resources

* log subscription

* fix(import): incorporate review

* fix(import): incorporate review

* docs: add suggestion for import

Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>

* fix(import): add verification otp event and handling of deleted but existing users

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
Co-authored-by: Fabienne <fabienne.gerschwiler@gmail.com>
Co-authored-by: Silvan <silvan.reusser@gmail.com>
Co-authored-by: Fabi <38692350+hifabienne@users.noreply.github.com>
2022-07-28 13:42:35 +00:00

270 lines
10 KiB
Go

package command
import (
"context"
"reflect"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/project"
"github.com/zitadel/zitadel/internal/telemetry/tracing"
)
func (c *Commands) AddProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
existingMember, err := c.projectGrantWriteModelByID(ctx, grantID, grant.AggregateID, resourceOwner)
if err != nil && !caos_errs.IsNotFound(err) {
return nil, err
}
if existingMember != nil && existingMember.State != domain.ProjectGrantStateUnspecified {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-2b8fs", "Errors.Project.Grant.AlreadyExisting")
}
return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
}
func (c *Commands) AddProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string) (_ *domain.ProjectGrant, err error) {
if !grant.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-3b8fs", "Errors.Project.Grant.Invalid")
}
err = c.checkProjectGrantPreCondition(ctx, grant)
if err != nil {
return nil, err
}
grantID, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
return c.addProjectGrantWithID(ctx, grant, grantID, resourceOwner)
}
func (c *Commands) addProjectGrantWithID(ctx context.Context, grant *domain.ProjectGrant, grantID string, resourceOwner string) (_ *domain.ProjectGrant, err error) {
grant.GrantID = grantID
addedGrant := NewProjectGrantWriteModel(grant.GrantID, grant.AggregateID, resourceOwner)
projectAgg := ProjectAggregateFromWriteModel(&addedGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(
ctx,
project.NewGrantAddedEvent(ctx, projectAgg, grant.GrantID, grant.GrantedOrgID, grant.RoleKeys))
if err != nil {
return nil, err
}
err = AppendAndReduce(addedGrant, pushedEvents...)
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(addedGrant), nil
}
func (c *Commands) ChangeProjectGrant(ctx context.Context, grant *domain.ProjectGrant, resourceOwner string, cascadeUserGrantIDs ...string) (_ *domain.ProjectGrant, err error) {
if grant.GrantID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1j83s", "Errors.IDMissing")
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grant.GrantID, grant.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
grant.GrantedOrgID = existingGrant.GrantedOrgID
err = c.checkProjectGrantPreCondition(ctx, grant)
if err != nil {
return nil, err
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
if reflect.DeepEqual(existingGrant.RoleKeys, grant.RoleKeys) {
return nil, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-0o0pL", "Errors.NoChangesFoundc")
}
events := []eventstore.Command{
project.NewGrantChangedEvent(ctx, projectAgg, grant.GrantID, grant.RoleKeys),
}
removedRoles := domain.GetRemovedRoles(existingGrant.RoleKeys, grant.RoleKeys)
if len(removedRoles) == 0 {
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingGrant, pushedEvents...)
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(existingGrant), nil
}
for _, userGrantID := range cascadeUserGrantIDs {
event, err := c.removeRoleFromUserGrant(ctx, userGrantID, removedRoles, true)
if err != nil {
continue
}
events = append(events, event)
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingGrant, pushedEvents...)
if err != nil {
return nil, err
}
return projectGrantWriteModelToProjectGrant(existingGrant), nil
}
func (c *Commands) removeRoleFromProjectGrant(ctx context.Context, projectAgg *eventstore.Aggregate, projectID, projectGrantID, roleKey string, cascade bool) (_ eventstore.Command, _ *ProjectGrantWriteModel, err error) {
existingProjectGrant, err := c.projectGrantWriteModelByID(ctx, projectGrantID, projectID, "")
if err != nil {
return nil, nil, err
}
if existingProjectGrant.State == domain.ProjectGrantStateUnspecified || existingProjectGrant.State == domain.ProjectGrantStateRemoved {
return nil, nil, caos_errs.ThrowNotFound(nil, "COMMAND-3M9sd", "Errors.Project.Grant.NotFound")
}
keyExists := false
for i, key := range existingProjectGrant.RoleKeys {
if key == roleKey {
keyExists = true
copy(existingProjectGrant.RoleKeys[i:], existingProjectGrant.RoleKeys[i+1:])
existingProjectGrant.RoleKeys[len(existingProjectGrant.RoleKeys)-1] = ""
existingProjectGrant.RoleKeys = existingProjectGrant.RoleKeys[:len(existingProjectGrant.RoleKeys)-1]
continue
}
}
if !keyExists {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-5m8g9", "Errors.Project.Grant.RoleKeyNotFound")
}
changedProjectGrant := NewProjectGrantWriteModel(projectGrantID, projectID, existingProjectGrant.ResourceOwner)
if cascade {
return project.NewGrantCascadeChangedEvent(ctx, projectAgg, projectGrantID, existingProjectGrant.RoleKeys), changedProjectGrant, nil
}
return project.NewGrantChangedEvent(ctx, projectAgg, projectGrantID, existingProjectGrant.RoleKeys), changedProjectGrant, nil
}
func (c *Commands) DeactivateProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return details, err
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, projectID, resourceOwner)
if err != nil {
return details, err
}
if existingGrant.State != domain.ProjectGrantStateActive {
return details, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-47fu8", "Errors.Project.Grant.NotActive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewGrantDeactivateEvent(ctx, projectAgg, grantID))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingGrant, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
func (c *Commands) ReactivateProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-p0s4V", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return details, err
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, projectID, resourceOwner)
if err != nil {
return details, err
}
if existingGrant.State != domain.ProjectGrantStateInactive {
return details, caos_errs.ThrowPreconditionFailed(nil, "PROJECT-47fu8", "Errors.Project.Grant.NotInactive")
}
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, project.NewGrantReactivatedEvent(ctx, projectAgg, grantID))
if err != nil {
return nil, err
}
err = AppendAndReduce(existingGrant, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
func (c *Commands) RemoveProjectGrant(ctx context.Context, projectID, grantID, resourceOwner string, cascadeUserGrantIDs ...string) (details *domain.ObjectDetails, err error) {
if grantID == "" || projectID == "" {
return details, caos_errs.ThrowInvalidArgument(nil, "PROJECT-1m9fJ", "Errors.IDMissing")
}
err = c.checkProjectExists(ctx, projectID, resourceOwner)
if err != nil {
return details, caos_errs.ThrowPreconditionFailed(err, "PROJECT-6mf9s", "Errors.Project.NotFound")
}
existingGrant, err := c.projectGrantWriteModelByID(ctx, grantID, projectID, resourceOwner)
if err != nil {
return details, err
}
events := make([]eventstore.Command, 0)
projectAgg := ProjectAggregateFromWriteModel(&existingGrant.WriteModel)
events = append(events, project.NewGrantRemovedEvent(ctx, projectAgg, grantID, existingGrant.GrantedOrgID))
for _, userGrantID := range cascadeUserGrantIDs {
event, _, err := c.removeUserGrant(ctx, userGrantID, "", true)
if err != nil {
logging.LogWithFields("COMMAND-3m8sG", "usergrantid", grantID).WithError(err).Warn("could not cascade remove user grant")
continue
}
events = append(events, event)
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
}
err = AppendAndReduce(existingGrant, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&existingGrant.WriteModel), nil
}
func (c *Commands) projectGrantWriteModelByID(ctx context.Context, grantID, projectID, resourceOwner string) (member *ProjectGrantWriteModel, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()
writeModel := NewProjectGrantWriteModel(grantID, projectID, resourceOwner)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
if writeModel.State == domain.ProjectGrantStateUnspecified || writeModel.State == domain.ProjectGrantStateRemoved {
return nil, caos_errs.ThrowNotFound(nil, "PROJECT-D8JxR", "Errors.Project.Grant.NotFound")
}
return writeModel, nil
}
func (c *Commands) checkProjectGrantPreCondition(ctx context.Context, projectGrant *domain.ProjectGrant) error {
preConditions := NewProjectGrantPreConditionReadModel(projectGrant.AggregateID, projectGrant.GrantedOrgID)
err := c.eventstore.FilterToQueryReducer(ctx, preConditions)
if err != nil {
return err
}
if !preConditions.ProjectExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-m9gsd", "Errors.Project.NotFound")
}
if !preConditions.GrantedOrgExists {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-3m9gg", "Errors.Org.NotFound")
}
if projectGrant.HasInvalidRoles(preConditions.ExistingRoleKeys) {
return caos_errs.ThrowPreconditionFailed(err, "COMMAND-6m9gd", "Errors.Project.Role.NotFound")
}
return nil
}