diff --git a/api/http/common.go b/api/http/common.go index 5f38d4905d..7a02050b7d 100644 --- a/api/http/common.go +++ b/api/http/common.go @@ -233,7 +233,8 @@ func EncodeError(_ context.Context, err error, w http.ResponseWriter) { errors.Contains(err, apiutil.ErrMissingRoleMembers), errors.Contains(err, apiutil.ErrMissingDescription), errors.Contains(err, apiutil.ErrMissingEntityID), - errors.Contains(err, apiutil.ErrInvalidRouteFormat): + errors.Contains(err, apiutil.ErrInvalidRouteFormat), + errors.Contains(err, svcerr.ErrRetainOneMember): err = unwrap(err) w.WriteHeader(http.StatusBadRequest) diff --git a/domains/service.go b/domains/service.go index 68f2d06623..a2346b6cb6 100644 --- a/domains/service.go +++ b/domains/service.go @@ -351,15 +351,40 @@ func (svc *service) RemoveEntityMembers(ctx context.Context, session authn.Sessi return svc.ProvisionManageService.RemoveEntityMembers(ctx, session, entityID, members) } -func (svc *service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleID string, members []string) (err error) { - for _, member := range members { - if err := svc.repo.DeleteInvitation(ctx, member, entityID); err != nil && err != repoerr.ErrNotFound { +func (svc *service) RoleRemoveMembers(ctx context.Context, session authn.Session, entityID, roleID string, members []string) error { + ro, err := svc.repo.RetrieveEntityRole(ctx, entityID, roleID) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if _, err := svc.ProvisionManageService.BuiltInRoleActions(roles.BuiltInRoleName(ro.Name)); err == nil { + membersPage, err := svc.repo.RoleListMembers(ctx, ro.ID, 0, 0) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + if membersPage.Total <= uint64(len(members)) { + return svcerr.ErrRetainOneMember + } + } + + for _, memberID := range members { + if err := svc.repo.DeleteInvitation(ctx, memberID, entityID); err != nil && err != repoerr.ErrNotFound { return err } } + return svc.ProvisionManageService.RoleRemoveMembers(ctx, session, entityID, roleID, members) } -func (svc *service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) (err error) { - return svcerr.ErrNotFound +func (svc *service) RoleRemoveAllMembers(ctx context.Context, session authn.Session, entityID, roleID string) error { + ro, err := svc.repo.RetrieveEntityRole(ctx, entityID, roleID) + if err != nil { + return errors.Wrap(svcerr.ErrViewEntity, err) + } + + if _, err := svc.ProvisionManageService.BuiltInRoleActions(roles.BuiltInRoleName(ro.Name)); err == nil { + return svcerr.ErrRetainOneMember + } + + return svc.ProvisionManageService.RoleRemoveAllMembers(ctx, session, entityID, roleID) } diff --git a/pkg/errors/service/types.go b/pkg/errors/service/types.go index 928a2268b4..cbd353205c 100644 --- a/pkg/errors/service/types.go +++ b/pkg/errors/service/types.go @@ -87,4 +87,7 @@ var ( // ErrUnauthorizedPAT indicates failure occurred while authorizing PAT. ErrUnauthorizedPAT = errors.New("failed to authorize PAT") + + // ErrRetainOneMember indicates that at least one owner must be retained in the entity. + ErrRetainOneMember = errors.New("must retain at least one member") ) diff --git a/pkg/roles/provisionmanage.go b/pkg/roles/provisionmanage.go index 4481bd749d..2a20e47cc5 100644 --- a/pkg/roles/provisionmanage.go +++ b/pkg/roles/provisionmanage.go @@ -49,6 +49,14 @@ func NewProvisionManageService(entityType string, repo Repository, policy polici return rm, nil } +func (pms ProvisionManageService) BuiltInRoleActions(name BuiltInRoleName) ([]Action, error) { + actions, ok := pms.builtInRoles[name] + if !ok { + return nil, errors.Wrap(svcerr.ErrNotFound, fmt.Errorf("role %s not found", name)) + } + return actions, nil +} + func toRolesActions(actions []string) []Action { roActions := []Action{} for _, action := range actions {