mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-03 12:52:16 +00:00
feat: allow global org users to create org and self delete (#2759)
* fix: grant PROJECT_OWNER_VIEWER_GLOBAL org.create permission * Update authz.yaml * feat: delete my user * console things * lint * signout after deletion * stylelint rule * Update authz.yaml * Update authz.yaml * setup step * role SELF_MANAGEMENT_GLOBAL setup * fix: change default role on global org * Apply suggestions from code review Co-authored-by: Fabi <[email protected]> * Update console/src/assets/i18n/it.json Co-authored-by: Fabi <[email protected]> Co-authored-by: Max Peintner <[email protected]> Co-authored-by: Fabi <[email protected]>
This commit is contained in:
co-authored by
Fabi
Max Peintner
parent
7ea93bd5b6
commit
43f15953c3
@@ -11,7 +11,9 @@ import (
|
||||
obj_grpc "github.com/caos/zitadel/internal/api/grpc/object"
|
||||
"github.com/caos/zitadel/internal/api/grpc/org"
|
||||
user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
user_model "github.com/caos/zitadel/internal/user/model"
|
||||
grant_model "github.com/caos/zitadel/internal/usergrant/model"
|
||||
auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
|
||||
)
|
||||
@@ -24,6 +26,25 @@ func (s *Server) GetMyUser(ctx context.Context, _ *auth_pb.GetMyUserRequest) (*a
|
||||
return &auth_pb.GetMyUserResponse{User: user_grpc.UserToPb(user)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMyUser(ctx context.Context, _ *auth_pb.RemoveMyUserRequest) (*auth_pb.RemoveMyUserResponse, error) {
|
||||
ctxData := authz.GetCtxData(ctx)
|
||||
grants, err := s.repo.SearchMyUserGrants(ctx, &grant_model.UserGrantSearchRequest{Queries: []*grant_model.UserGrantSearchQuery{}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
membersShips, err := s.repo.SearchMyUserMemberships(ctx, &user_model.UserMembershipSearchRequest{Queries: []*user_model.UserMembershipSearchQuery{}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.RemoveUser(ctx, ctxData.UserID, ctxData.ResourceOwner, UserMembershipViewsToDomain(membersShips.Result), userGrantsToIDs(grants.Result)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &auth_pb.RemoveMyUserResponse{
|
||||
Details: obj_grpc.DomainToChangeDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserChangesRequest) (*auth_pb.ListMyUserChangesResponse, error) {
|
||||
sequence, limit, asc := change.ChangeQueryToModel(req.Query)
|
||||
features, err := s.query.FeaturesByOrgID(ctx, authz.GetCtxData(ctx).ResourceOwner)
|
||||
@@ -137,3 +158,46 @@ func ListMyProjectOrgsRequestToModel(req *auth_pb.ListMyProjectOrgsRequest) (*gr
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UserMembershipViewsToDomain(memberships []*user_model.UserMembershipView) []*domain.UserMembership {
|
||||
result := make([]*domain.UserMembership, len(memberships))
|
||||
for i, membership := range memberships {
|
||||
result[i] = &domain.UserMembership{
|
||||
UserID: membership.UserID,
|
||||
MemberType: MemberTypeToDomain(membership.MemberType),
|
||||
AggregateID: membership.AggregateID,
|
||||
ObjectID: membership.ObjectID,
|
||||
Roles: membership.Roles,
|
||||
DisplayName: membership.DisplayName,
|
||||
CreationDate: membership.CreationDate,
|
||||
ChangeDate: membership.ChangeDate,
|
||||
ResourceOwner: membership.ResourceOwner,
|
||||
ResourceOwnerName: membership.ResourceOwnerName,
|
||||
Sequence: membership.Sequence,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MemberTypeToDomain(mType user_model.MemberType) domain.MemberType {
|
||||
switch mType {
|
||||
case user_model.MemberTypeIam:
|
||||
return domain.MemberTypeIam
|
||||
case user_model.MemberTypeOrganisation:
|
||||
return domain.MemberTypeOrganisation
|
||||
case user_model.MemberTypeProject:
|
||||
return domain.MemberTypeProject
|
||||
case user_model.MemberTypeProjectGrant:
|
||||
return domain.MemberTypeProjectGrant
|
||||
default:
|
||||
return domain.MemberTypeUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func userGrantsToIDs(userGrants []*grant_model.UserGrantView) []string {
|
||||
converted := make([]string, len(userGrants))
|
||||
for i, grant := range userGrants {
|
||||
converted[i] = grant.ID
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type Step21 struct{}
|
||||
|
||||
func (s *Step21) Step() domain.Step {
|
||||
return domain.Step21
|
||||
}
|
||||
|
||||
func (s *Step21) execute(ctx context.Context, commandSide *Commands) error {
|
||||
return commandSide.SetupStep21(ctx, s)
|
||||
}
|
||||
|
||||
func (c *Commands) SetupStep21(ctx context.Context, step *Step21) error {
|
||||
fn := func(iam *IAMWriteModel) ([]eventstore.EventPusher, error) {
|
||||
events := make([]eventstore.EventPusher, 0)
|
||||
globalMembers := newGlobalOrgMemberWriteModel(iam.GlobalOrgID, domain.RoleOrgProjectCreator)
|
||||
orgAgg := OrgAggregateFromWriteModel(&globalMembers.WriteModel)
|
||||
if err := c.eventstore.FilterToQueryReducer(ctx, globalMembers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for userID, roles := range globalMembers.members {
|
||||
for i, role := range roles {
|
||||
if role == domain.RoleOrgProjectCreator {
|
||||
roles[i] = domain.RoleSelfManagementGlobal
|
||||
}
|
||||
}
|
||||
events = append(events, org.NewMemberChangedEvent(ctx, orgAgg, userID, roles...))
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
return c.setup(ctx, step, fn)
|
||||
}
|
||||
|
||||
type globalOrgMembersWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
orgID string
|
||||
role string
|
||||
members map[string][]string
|
||||
}
|
||||
|
||||
func newGlobalOrgMemberWriteModel(orgID, role string) *globalOrgMembersWriteModel {
|
||||
return &globalOrgMembersWriteModel{
|
||||
orgID: orgID,
|
||||
role: role,
|
||||
members: make(map[string][]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *globalOrgMembersWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *org.MemberAddedEvent:
|
||||
for _, role := range e.Roles {
|
||||
if wm.role == role {
|
||||
wm.members[e.UserID] = e.Roles
|
||||
break
|
||||
}
|
||||
}
|
||||
case *org.MemberChangedEvent:
|
||||
delete(wm.members, e.UserID)
|
||||
for _, role := range e.Roles {
|
||||
if wm.role == role {
|
||||
wm.members[e.UserID] = e.Roles
|
||||
break
|
||||
}
|
||||
}
|
||||
case *org.MemberRemovedEvent:
|
||||
delete(wm.members, e.UserID)
|
||||
case *org.MemberCascadeRemovedEvent:
|
||||
delete(wm.members, e.UserID)
|
||||
case *user.UserRemovedEvent:
|
||||
delete(wm.members, e.Aggregate().ID)
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *globalOrgMembersWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.orgID).
|
||||
EventTypes(
|
||||
org.MemberAddedEventType,
|
||||
org.MemberChangedEventType,
|
||||
org.MemberRemovedEventType,
|
||||
org.MemberCascadeRemovedEventType,
|
||||
).
|
||||
Or().
|
||||
AggregateTypes(user.AggregateType).
|
||||
EventTypes(user.UserRemovedType).
|
||||
Builder()
|
||||
}
|
||||
@@ -7,15 +7,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
IAMRolePrefix = "IAM"
|
||||
OrgRolePrefix = "ORG"
|
||||
ProjectRolePrefix = "PROJECT"
|
||||
ProjectGrantRolePrefix = "PROJECT_GRANT"
|
||||
RoleOrgOwner = "ORG_OWNER"
|
||||
RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
|
||||
RoleIAMOwner = "IAM_OWNER"
|
||||
RoleProjectOwner = "PROJECT_OWNER"
|
||||
RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
|
||||
IAMRolePrefix = "IAM"
|
||||
OrgRolePrefix = "ORG"
|
||||
ProjectRolePrefix = "PROJECT"
|
||||
ProjectGrantRolePrefix = "PROJECT_GRANT"
|
||||
RoleOrgOwner = "ORG_OWNER"
|
||||
RoleOrgProjectCreator = "ORG_PROJECT_CREATOR"
|
||||
RoleIAMOwner = "IAM_OWNER"
|
||||
RoleProjectOwner = "PROJECT_OWNER"
|
||||
RoleProjectOwnerGlobal = "PROJECT_OWNER_GLOBAL"
|
||||
RoleSelfManagementGlobal = "SELF_MANAGEMENT_GLOBAL"
|
||||
)
|
||||
|
||||
func CheckForInvalidRoles(roles []string, rolePrefix string, validRoles []authz.RoleMapping) []string {
|
||||
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
Step18
|
||||
Step19
|
||||
Step20
|
||||
Step21
|
||||
//StepCount marks the the length of possible steps (StepCount-1 == last possible step)
|
||||
StepCount
|
||||
)
|
||||
|
||||
@@ -3,9 +3,10 @@ package project
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
|
||||
@@ -26,6 +26,7 @@ type IAMSetUp struct {
|
||||
Step18 *command.Step18
|
||||
Step19 *command.Step19
|
||||
Step20 *command.Step20
|
||||
Step21 *command.Step21
|
||||
}
|
||||
|
||||
func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
|
||||
@@ -52,6 +53,7 @@ func (setup *IAMSetUp) Steps(currentDone domain.Step) ([]command.Step, error) {
|
||||
setup.Step18,
|
||||
setup.Step19,
|
||||
setup.Step20,
|
||||
setup.Step21,
|
||||
} {
|
||||
if step.Step() <= currentDone {
|
||||
continue
|
||||
|
||||
@@ -259,7 +259,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
||||
}
|
||||
|
||||
resourceOwner := iam.GlobalOrgID
|
||||
memberRoles := []string{domain.RoleOrgProjectCreator}
|
||||
memberRoles := []string{domain.RoleSelfManagementGlobal}
|
||||
|
||||
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID {
|
||||
memberRoles = nil
|
||||
|
||||
@@ -136,7 +136,7 @@ func (l *Login) handleExternalUserRegister(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, iam *iam_model.IAM, user *domain.Human, externalIDP *domain.UserIDPLink) {
|
||||
resourceOwner := iam.GlobalOrgID
|
||||
memberRoles := []string{domain.RoleOrgProjectCreator}
|
||||
memberRoles := []string{domain.RoleSelfManagementGlobal}
|
||||
|
||||
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != resourceOwner {
|
||||
memberRoles = nil
|
||||
@@ -196,7 +196,7 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
resourceOwner := iam.GlobalOrgID
|
||||
memberRoles := []string{domain.RoleOrgProjectCreator}
|
||||
memberRoles := []string{domain.RoleSelfManagementGlobal}
|
||||
|
||||
if authReq.RequestedOrgID != "" && authReq.RequestedOrgID != iam.GlobalOrgID {
|
||||
memberRoles = nil
|
||||
|
||||
@@ -68,7 +68,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
resourceOwner := iam.GlobalOrgID
|
||||
memberRoles := []string{domain.RoleOrgProjectCreator}
|
||||
memberRoles := []string{domain.RoleSelfManagementGlobal}
|
||||
|
||||
if authRequest.RequestedOrgID != "" && authRequest.RequestedOrgID != iam.GlobalOrgID {
|
||||
memberRoles = nil
|
||||
|
||||
Reference in New Issue
Block a user