diff --git a/api/auth/user/UserRestHandler.go b/api/auth/user/UserRestHandler.go index 187030b220f..c3084b9c752 100644 --- a/api/auth/user/UserRestHandler.go +++ b/api/auth/user/UserRestHandler.go @@ -22,6 +22,8 @@ import ( util2 "github.com/devtron-labs/devtron/api/auth/user/util" "github.com/devtron-labs/devtron/pkg/auth/user/helper" "github.com/devtron-labs/devtron/pkg/auth/user/repository" + util3 "github.com/devtron-labs/devtron/pkg/auth/user/util" + "github.com/devtron-labs/devtron/pkg/team" "github.com/go-pg/pg" "github.com/gorilla/schema" "net/http" @@ -77,11 +79,13 @@ type UserRestHandlerImpl struct { enforcer casbin.Enforcer roleGroupService user2.RoleGroupService userCommonService user2.UserCommonService + teamService team.TeamService } func NewUserRestHandlerImpl(userService user2.UserService, validator *validator.Validate, logger *zap.SugaredLogger, enforcer casbin.Enforcer, roleGroupService user2.RoleGroupService, - userCommonService user2.UserCommonService) *UserRestHandlerImpl { + userCommonService user2.UserCommonService, + teamService team.TeamService) *UserRestHandlerImpl { userAuthHandler := &UserRestHandlerImpl{ userService: userService, validator: validator, @@ -89,6 +93,7 @@ func NewUserRestHandlerImpl(userService user2.UserService, validator *validator. enforcer: enforcer, roleGroupService: roleGroupService, userCommonService: userCommonService, + teamService: teamService, } return userAuthHandler } @@ -242,24 +247,19 @@ func (handler UserRestHandlerImpl) GetById(w http.ResponseWriter, r *http.Reques // NOTE: if no role assigned, user will be visible to all manager. // RBAC enforcer applying filteredRoleFilter := make([]bean.RoleFilter, 0) + isManagerOfAnyApp := false if res.RoleFilters != nil && len(res.RoleFilters) > 0 { - for _, filter := range res.RoleFilters { - authPass := true - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); !ok { - authPass = false - } - } - if filter.Entity == bean2.CLUSTER_ENTITIY { - if ok := handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth); !ok { - authPass = false - } - } - if authPass { - filteredRoleFilter = append(filteredRoleFilter, filter) - } + isManagerOfAnyApp, err = handler.CheckManagerOfAnyAppAccess(token) + if err != nil { + handler.logger.Errorw("rbac Check error, GetById", "err", err, "id", id) + common.WriteJsonResp(w, err, "Failed to get by id", http.StatusInternalServerError) + return } } + // sending all permission in case of super admin or manager of any app + if res.SuperAdmin || isManagerOfAnyApp { + filteredRoleFilter = res.RoleFilters + } for index, roleFilter := range filteredRoleFilter { if roleFilter.Entity == "" { filteredRoleFilter[index].Entity = bean2.ENTITY_APPS @@ -274,6 +274,22 @@ func (handler UserRestHandlerImpl) GetById(w http.ResponseWriter, r *http.Reques common.WriteJsonResp(w, err, res, http.StatusOK) } +func (handler UserRestHandlerImpl) CheckManagerOfAnyAppAccess(token string) (bool, error) { + var isManagerOfAnyApp bool + teams, err := handler.teamService.FetchAllActive() + if err != nil { + handler.logger.Errorw("error encountered in CheckManagerOfAnyAppAccess", "err", err) + return false, err + } + for _, project := range teams { + if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, project.Name); ok { + isManagerOfAnyApp = true + break + } + } + return isManagerOfAnyApp, nil +} + func (handler UserRestHandlerImpl) GetAllV2(w http.ResponseWriter, r *http.Request) { var decoder = schema.NewDecoder() userId, err := handler.userService.GetLoggedInUser(r) @@ -577,24 +593,18 @@ func (handler UserRestHandlerImpl) FetchRoleGroupById(w http.ResponseWriter, r * // RBAC enforcer applying token := r.Header.Get("token") filteredRoleFilter := make([]bean.RoleFilter, 0) + isManagerOfAnyApp := false if res.RoleFilters != nil && len(res.RoleFilters) > 0 { - for _, filter := range res.RoleFilters { - authPass := true - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); !ok { - authPass = false - } - } - if filter.Entity == bean2.CLUSTER_ENTITIY { - if isValidAuth := handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth); !isValidAuth { - authPass = false - } - } - if authPass { - filteredRoleFilter = append(filteredRoleFilter, filter) - } + isManagerOfAnyApp, err = handler.CheckManagerOfAnyAppAccess(token) + if err != nil { + handler.logger.Errorw("rbac Check error, GetById", "err", err, "id", id) + common.WriteJsonResp(w, err, "Failed to get by id", http.StatusInternalServerError) + return } } + if isManagerOfAnyApp || res.SuperAdmin { + filteredRoleFilter = res.RoleFilters + } for index, roleFilter := range filteredRoleFilter { if roleFilter.Entity == "" { filteredRoleFilter[index].Entity = bean2.ENTITY_APPS @@ -1123,19 +1133,14 @@ func (handler UserRestHandlerImpl) checkRBACForUserCreate(token string, requestS if !isAuthorised { if roleFilters != nil && len(roleFilters) > 0 { //auth check inside roleFilters for _, filter := range roleFilters { - switch { - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: + if filter.Entity == bean.CHART_GROUP_ENTITY && len(roleFilters) == 1 { + //if only chartGroup entity is present in request then access will be judged through super-admin access isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY && len(roleFilters) == 1: //if only chartGroup entity is present in request then access will be judged through super-admin access - isAuthorised = isActionUserSuperAdmin - case filter.Entity == bean.CHART_GROUP_ENTITY && len(roleFilters) > 1: //if entities apart from chartGroup entity are present, not checking chartGroup access + } else if filter.Entity == bean.CHART_GROUP_ENTITY && len(roleFilters) > 1 { + //if entities apart from chartGroup entity are present, not checking chartGroup access isAuthorised = true - default: - isAuthorised = false + } else { + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) } if !isAuthorised { break @@ -1150,21 +1155,16 @@ func (handler UserRestHandlerImpl) checkRBACForUserCreate(token string, requestS } if len(groupRoles) > 0 { for _, groupRole := range groupRoles { - switch { - case groupRole.Action == bean.ACTION_SUPERADMIN: + if groupRole.Action == bean.ACTION_SUPERADMIN { isAuthorised = isActionUserSuperAdmin - case groupRole.AccessType == bean.APP_ACCESS_TYPE_HELM || groupRole.Entity == bean2.EntityJobs: + } else if groupRole.Entity == bean.CHART_GROUP_ENTITY && len(groupRoles) == 1 { + //if only chartGroup entity is present in request then access will be judged through super-admin access isAuthorised = isActionUserSuperAdmin - case len(groupRole.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, groupRole.Team) - case groupRole.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(groupRole.Cluster, groupRole.Namespace, groupRole.Group, groupRole.Kind, groupRole.Resource, token, handler.CheckManagerAuth) - case groupRole.Entity == bean.CHART_GROUP_ENTITY && len(groupRoles) == 1: //if only chartGroup entity is present in request then access will be judged through super-admin access - isAuthorised = isActionUserSuperAdmin - case groupRole.Entity == bean.CHART_GROUP_ENTITY && len(groupRoles) > 1: //if entities apart from chartGroup entity are present, not checking chartGroup access + } else if groupRole.Entity == bean.CHART_GROUP_ENTITY && len(groupRoles) > 1 { + //if entities apart from chartGroup entity are present, not checking chartGroup access isAuthorised = true - default: - isAuthorised = false + } else { + isAuthorised = handler.CheckRbacForARole(token, *groupRole, isActionUserSuperAdmin) } if !isAuthorised { break @@ -1179,7 +1179,7 @@ func (handler UserRestHandlerImpl) checkRBACForUserCreate(token string, requestS } func (handler UserRestHandlerImpl) checkRBACForUserUpdate(token string, userInfo *bean.UserInfo, isUserAlreadySuperAdmin bool, eliminatedRoleFilters, - eliminatedGroupRoles []*repository.RoleModel) (isAuthorised bool, err error) { + eliminatedGroupRoles []*repository.RoleModel, mapOfExistingRoleFilter, mapOfExistingUserGroup map[string]bool) (isAuthorised bool, err error) { isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") requestSuperAdmin := userInfo.SuperAdmin if (requestSuperAdmin || isUserAlreadySuperAdmin) && !isActionUserSuperAdmin { @@ -1193,18 +1193,11 @@ func (handler UserRestHandlerImpl) checkRBACForUserUpdate(token string, userInfo if !isAuthorised { if roleFilters != nil && len(roleFilters) > 0 { //auth check inside roleFilters for _, filter := range roleFilters { - switch { - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: - isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY: + if _, ok := mapOfExistingRoleFilter[util3.GetUniqueKeyForRoleFilter(filter)]; ok { isAuthorised = true - default: - isAuthorised = false + continue } + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) if !isAuthorised { break } @@ -1212,59 +1205,67 @@ func (handler UserRestHandlerImpl) checkRBACForUserUpdate(token string, userInfo } if eliminatedRolesToBeChecked != nil && len(eliminatedRolesToBeChecked) > 0 { for _, filter := range eliminatedRolesToBeChecked { - switch { - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: - isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY: - isAuthorised = true - default: - isAuthorised = false - } + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) if !isAuthorised { break } } } if len(roleGroups) > 0 { // auth check inside groups - groupRoles, err := handler.roleGroupService.FetchRolesForUserRoleGroups(roleGroups) - if err != nil && err != pg.ErrNoRows { - handler.logger.Errorw("service err, UpdateUser", "err", err, "payload", roleGroups) - return false, err + filteredRoleGroupsForRbac := make([]bean.UserRoleGroup, 0, len(roleGroups)) + for _, group := range roleGroups { + if _, ok := mapOfExistingUserGroup[helper.GetUniqueKeyForUserGroup(group)]; !ok { + filteredRoleGroupsForRbac = append(filteredRoleGroupsForRbac, group) + } } - if len(groupRoles) > 0 { - for _, groupRole := range groupRoles { - switch { - case groupRole.Action == bean.ACTION_SUPERADMIN: - isAuthorised = isActionUserSuperAdmin - case groupRole.AccessType == bean.APP_ACCESS_TYPE_HELM || groupRole.Entity == bean2.EntityJobs: - isAuthorised = isActionUserSuperAdmin - case len(groupRole.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, groupRole.Team) - case groupRole.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(groupRole.Cluster, groupRole.Namespace, groupRole.Group, groupRole.Kind, groupRole.Resource, token, handler.CheckManagerAuth) - case groupRole.Entity == bean.CHART_GROUP_ENTITY: - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - break + if len(filteredRoleGroupsForRbac) == 0 { + groupRoles, err := handler.roleGroupService.FetchRolesForUserRoleGroups(roleGroups) + if err != nil && err != pg.ErrNoRows { + handler.logger.Errorw("service err, UpdateUser", "err", err, "payload", roleGroups) + return false, err + } + if len(groupRoles) > 0 { + for _, groupRole := range groupRoles { + if groupRole.Action == bean.ACTION_SUPERADMIN { + isAuthorised = isActionUserSuperAdmin + } else { + isAuthorised = handler.CheckRbacForARole(token, *groupRole, isActionUserSuperAdmin) + } + if !isAuthorised { + break + } } + } else { + isAuthorised = false } } else { - isAuthorised = false + // if all exiting role groups , setting to true (as no change) + isAuthorised = true } } } return isAuthorised, nil } +func (handler UserRestHandlerImpl) CheckRbacForARole(token string, entity bean.RoleEntity, isActionUserSuperAdmin bool) bool { + isAuthorised := false + switch { + case entity.GetAccessType() == bean.APP_ACCESS_TYPE_HELM || entity.GetEntity() == bean2.EntityJobs: + isAuthorised = isActionUserSuperAdmin + case len(entity.GetTeam()) > 0: + isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, entity.GetTeam()) + case entity.GetEntity() == bean.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(entity.GetCluster(), entity.GetNamespace(), entity.GetGroup(), entity.GetKind(), entity.GetResource(), token, handler.CheckManagerAuth) + case entity.GetEntity() == bean.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + return isAuthorised +} + func (handler UserRestHandlerImpl) checkRBACForRoleGroupUpdate(token string, groupInfo *bean.RoleGroup, - eliminatedRoleFilters []*repository.RoleModel) (isAuthorised bool, err error) { + eliminatedRoleFilters []*repository.RoleModel, mapOfExitingRoleFiltersKey map[string]bool) (isAuthorised bool, err error) { isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") requestSuperAdmin := groupInfo.SuperAdmin if requestSuperAdmin && !isActionUserSuperAdmin { @@ -1275,19 +1276,14 @@ func (handler UserRestHandlerImpl) checkRBACForRoleGroupUpdate(token string, gro if !isAuthorised { if groupInfo.RoleFilters != nil && len(groupInfo.RoleFilters) > 0 { //auth check inside roleFilters for _, filter := range groupInfo.RoleFilters { - switch { - case filter.Action == bean.ACTION_SUPERADMIN: - isAuthorised = isActionUserSuperAdmin - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: - isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY: + if _, ok := mapOfExitingRoleFiltersKey[util3.GetUniqueKeyForRoleFilter(filter)]; ok { isAuthorised = true - default: - isAuthorised = false + continue + } + if filter.Action == bean.ACTION_SUPERADMIN { + isAuthorised = isActionUserSuperAdmin + } else { + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) } if !isAuthorised { break @@ -1296,18 +1292,7 @@ func (handler UserRestHandlerImpl) checkRBACForRoleGroupUpdate(token string, gro } if len(eliminatedRoleFilters) > 0 { for _, filter := range eliminatedRoleFilters { - switch { - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: - isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY: - isAuthorised = true - default: - isAuthorised = false - } + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) if !isAuthorised { break } @@ -1323,19 +1308,10 @@ func (handler UserRestHandlerImpl) checkRBACForRoleGroupDelete(token string, gro if !isAuthorised { if groupRoles != nil && len(groupRoles) > 0 { //auth check inside roleFilters for _, filter := range groupRoles { - switch { - case filter.Action == bean.ACTION_SUPERADMIN: - isAuthorised = isActionUserSuperAdmin - case filter.AccessType == bean.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: + if filter.Action == bean.ACTION_SUPERADMIN { isAuthorised = isActionUserSuperAdmin - case len(filter.Team) > 0: - isAuthorised = handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionCreate, filter.Team) - case filter.Entity == bean.CLUSTER_ENTITIY: - isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - case filter.Entity == bean.CHART_GROUP_ENTITY: - isAuthorised = true - default: - isAuthorised = false + } else { + isAuthorised = handler.CheckRbacForARole(token, filter, isActionUserSuperAdmin) } if !isAuthorised { break diff --git a/api/bean/UserRequest.go b/api/bean/UserRequest.go index 2ba15a68ca1..ae23dfb78ac 100644 --- a/api/bean/UserRequest.go +++ b/api/bean/UserRequest.go @@ -155,3 +155,47 @@ type BulkDeleteRequest struct { type UserRoleGroup struct { RoleGroup *RoleGroup `json:"roleGroup"` } + +type RoleEntity interface { + GetAccessType() string + GetEntity() string + GetTeam() string + GetCluster() string + GetNamespace() string + GetGroup() string + GetKind() string + GetResource() string +} + +// For RoleFilter +func (filter RoleFilter) GetAccessType() string { + return filter.AccessType +} + +func (filter RoleFilter) GetEntity() string { + return filter.Entity +} + +func (filter RoleFilter) GetTeam() string { + return filter.Team +} + +func (filter RoleFilter) GetCluster() string { + return filter.Cluster +} + +func (filter RoleFilter) GetNamespace() string { + return filter.Namespace +} + +func (filter RoleFilter) GetGroup() string { + return filter.Group +} + +func (filter RoleFilter) GetKind() string { + return filter.Kind +} + +func (filter RoleFilter) GetResource() string { + return filter.Resource +} diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 27ea1f7f1bb..d7de79328eb 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -256,7 +256,7 @@ func InitializeApp() (*App, error) { userAuthHandlerImpl := user2.NewUserAuthHandlerImpl(userAuthServiceImpl, validate, sugaredLogger, enforcerImpl) userAuthRouterImpl := user2.NewUserAuthRouterImpl(sugaredLogger, userAuthHandlerImpl, userAuthOidcHelperImpl) roleGroupServiceImpl := user.NewRoleGroupServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, userCommonServiceImpl) - userRestHandlerImpl := user2.NewUserRestHandlerImpl(userServiceImpl, validate, sugaredLogger, enforcerImpl, roleGroupServiceImpl, userCommonServiceImpl) + userRestHandlerImpl := user2.NewUserRestHandlerImpl(userServiceImpl, validate, sugaredLogger, enforcerImpl, roleGroupServiceImpl, userCommonServiceImpl, teamServiceImpl) userRouterImpl := user2.NewUserRouterImpl(userRestHandlerImpl) transactionUtilImpl := sql.NewTransactionUtilImpl(db) genericNoteRepositoryImpl := repository6.NewGenericNoteRepositoryImpl(db, transactionUtilImpl) diff --git a/pkg/auth/user/RoleGroupService.go b/pkg/auth/user/RoleGroupService.go index af96f5f6125..be0708f8045 100644 --- a/pkg/auth/user/RoleGroupService.go +++ b/pkg/auth/user/RoleGroupService.go @@ -19,6 +19,7 @@ package user import ( "errors" "fmt" + helper2 "github.com/devtron-labs/devtron/pkg/auth/user/helper" "github.com/devtron-labs/devtron/pkg/auth/user/repository/helper" "net/http" "strings" @@ -39,7 +40,7 @@ import ( type RoleGroupService interface { CreateRoleGroup(request *bean.RoleGroup) (*bean.RoleGroup, error) UpdateRoleGroup(request *bean.RoleGroup, token string, checkRBACForGroupUpdate func(token string, groupInfo *bean.RoleGroup, - eliminatedRoleFilters []*repository.RoleModel) (isAuthorised bool, err error)) (*bean.RoleGroup, error) + eliminatedRoleFilters []*repository.RoleModel, mapOfExistingRoleFilterKey map[string]bool) (isAuthorised bool, err error)) (*bean.RoleGroup, error) FetchDetailedRoleGroups(req *bean.ListingRequest) ([]*bean.RoleGroup, error) FetchRoleGroupsById(id int32) (*bean.RoleGroup, error) FetchRoleGroups() ([]*bean.RoleGroup, error) @@ -369,7 +370,7 @@ func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForJobsEntity(roleFilter } func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean.RoleGroup, token string, checkRBACForGroupUpdate func(token string, groupInfo *bean.RoleGroup, - eliminatedRoleFilters []*repository.RoleModel) (isAuthorised bool, err error)) (*bean.RoleGroup, error) { + eliminatedRoleFilters []*repository.RoleModel, mapOfExistingRoleFilterKey map[string]bool) (isAuthorised bool, err error)) (*bean.RoleGroup, error) { dbConnection := impl.roleGroupRepository.GetConnection() tx, err := dbConnection.Begin() if err != nil { @@ -476,7 +477,13 @@ func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean.RoleGroup, token } if checkRBACForGroupUpdate != nil { - isAuthorised, err := checkRBACForGroupUpdate(token, request, eliminatedRoleModels) + existingRoleGroupData, err := impl.FetchRoleGroupsById(roleGroup.Id) + if err != nil { + impl.logger.Errorw("error encountered in Update role group", "err", err, "roleGroupId", roleGroup.Id) + return nil, err + } + mapOfExitingRoleFiltersKey := helper2.GetMapOfUniqueKeys(existingRoleGroupData.RoleFilters, util2.GetUniqueKeyForRoleFilter) + isAuthorised, err := checkRBACForGroupUpdate(token, request, eliminatedRoleModels, mapOfExitingRoleFiltersKey) if err != nil { impl.logger.Errorw("error in checking RBAC for role group update", "err", err, "request", request) return nil, err diff --git a/pkg/auth/user/UserService.go b/pkg/auth/user/UserService.go index 6b8a741808b..c07496fa509 100644 --- a/pkg/auth/user/UserService.go +++ b/pkg/auth/user/UserService.go @@ -54,7 +54,7 @@ type UserService interface { CreateUser(userInfo *bean.UserInfo) ([]*bean.UserInfo, error) SelfRegisterUserIfNotExists(userInfo *bean.UserInfo) ([]*bean.UserInfo, error) UpdateUser(userInfo *bean.UserInfo, token string, checkRBACForUserUpdate func(token string, userInfo *bean.UserInfo, isUserAlreadySuperAdmin bool, - eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel) (isAuthorised bool, err error)) (*bean.UserInfo, error) + eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel, mapOfExistingRoleFilter, mapOfExistingUserGroup map[string]bool) (isAuthorised bool, err error)) (*bean.UserInfo, error) GetById(id int32) (*bean.UserInfo, error) GetAll() ([]bean.UserInfo, error) GetAllWithFilters(request *bean.ListingRequest) (*bean.UserListingResponse, error) @@ -572,13 +572,11 @@ func (impl *UserServiceImpl) mergeRoleFilter(oldR []bean.RoleFilter, newR []bean Resource: role.Resource, Workflow: role.Workflow, }) - key := fmt.Sprintf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s", role.Entity, role.Team, role.Environment, - role.EntityName, role.Action, role.AccessType, role.Cluster, role.Namespace, role.Group, role.Kind, role.Resource, role.Workflow) + key := util3.GetUniqueKeyForRoleFilter(role) keysMap[key] = true } for _, role := range newR { - key := fmt.Sprintf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s", role.Entity, role.Team, role.Environment, - role.EntityName, role.Action, role.AccessType, role.Cluster, role.Namespace, role.Group, role.Kind, role.Resource, role.Workflow) + key := util3.GetUniqueKeyForRoleFilter(role) if _, ok := keysMap[key]; !ok { roleFilters = append(roleFilters, bean.RoleFilter{ Entity: role.Entity, @@ -635,7 +633,7 @@ func (impl *UserServiceImpl) mergeUserRoleGroup(oldUserRoleGroups []bean.UserRol } func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, checkRBACForUserUpdate func(token string, userInfo *bean.UserInfo, - isUserAlreadySuperAdmin bool, eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel) (isAuthorised bool, err error)) (*bean.UserInfo, error) { + isUserAlreadySuperAdmin bool, eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel, mapOfExistingRoleFilter, mapOfExistingUserGroup map[string]bool) (isAuthorised bool, err error)) (*bean.UserInfo, error) { //checking if request for same user is being processed isLocked := impl.getUserReqLockStateById(userInfo.Id) if isLocked { @@ -802,7 +800,15 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c } if checkRBACForUserUpdate != nil { - isAuthorised, err := checkRBACForUserUpdate(token, userInfo, isUserSuperAdmin, eliminatedRoles, eliminatedGroupRoles) + // get existing permissions for user and ignore rbac for unchanged permissions whether direct permissions or group permissions + existingUserInfo, err := impl.GetById(model.Id) + if err != nil { + impl.logger.Errorw("error while fetching user from db", "error", err) + return nil, err + } + uniqueRolefilterKeyMap := userHelper.GetMapOfUniqueKeys(existingUserInfo.RoleFilters, util3.GetUniqueKeyForRoleFilter) + existingRoleGroupKeyMap := userHelper.GetMapOfUniqueKeys(existingUserInfo.UserRoleGroup, userHelper.GetUniqueKeyForUserGroup) + isAuthorised, err := checkRBACForUserUpdate(token, userInfo, isUserSuperAdmin, eliminatedRoles, eliminatedGroupRoles, uniqueRolefilterKeyMap, existingRoleGroupKeyMap) if err != nil { impl.logger.Errorw("error in checking RBAC for user update", "err", err, "userInfo", userInfo) return nil, err diff --git a/pkg/auth/user/helper/helper.go b/pkg/auth/user/helper/helper.go index 06f949a7c67..bf30b785310 100644 --- a/pkg/auth/user/helper/helper.go +++ b/pkg/auth/user/helper/helper.go @@ -93,3 +93,16 @@ func CreateErrorMessageForUserRoleGroups(restrictedGroups []bean2.RestrictedGrou } return errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin } + +// GetMapOfUniqueKeys takes a slice of any type and a function to extract a unique key, returning a map of unique keys. +func GetMapOfUniqueKeys[T any](items []T, getKeyFunc func(T) string) map[string]bool { + uniqueKeyMap := make(map[string]bool, len(items)) + for _, item := range items { + uniqueKeyMap[getKeyFunc(item)] = true + } + return uniqueKeyMap +} + +func GetUniqueKeyForUserGroup(group bean2.UserRoleGroup) string { + return fmt.Sprintf("%d-%s", group.RoleGroup.Id, group.RoleGroup.Name) +} diff --git a/pkg/auth/user/repository/UserAuthRepository.go b/pkg/auth/user/repository/UserAuthRepository.go index 6f647db6c28..de34970b7bc 100644 --- a/pkg/auth/user/repository/UserAuthRepository.go +++ b/pkg/auth/user/repository/UserAuthRepository.go @@ -110,6 +110,39 @@ type RoleModel struct { sql.AuditLog } +// For RoleModel +func (model RoleModel) GetAccessType() string { + return model.AccessType +} + +func (model RoleModel) GetEntity() string { + return model.Entity +} + +func (model RoleModel) GetTeam() string { + return model.Team +} + +func (model RoleModel) GetCluster() string { + return model.Cluster +} + +func (model RoleModel) GetNamespace() string { + return model.Namespace +} + +func (model RoleModel) GetGroup() string { + return model.Group +} + +func (model RoleModel) GetKind() string { + return model.Kind +} + +func (model RoleModel) GetResource() string { + return model.Resource +} + type RolePolicyDetails struct { Team string Env string diff --git a/pkg/auth/user/util/util.go b/pkg/auth/user/util/util.go index cdd7b4d2c91..37cdd2b0487 100644 --- a/pkg/auth/user/util/util.go +++ b/pkg/auth/user/util/util.go @@ -16,7 +16,11 @@ package util -import "strings" +import ( + "fmt" + "github.com/devtron-labs/devtron/api/bean" + "strings" +) const ( ApiTokenPrefix = "API-TOKEN:" @@ -39,3 +43,9 @@ func CheckIfAdminOrApiToken(email string) bool { func CheckIfApiToken(email string) bool { return strings.HasPrefix(email, ApiTokenPrefix) } + +func GetUniqueKeyForRoleFilter(role bean.RoleFilter) string { + key := fmt.Sprintf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-%s", role.Entity, role.Team, role.Environment, + role.EntityName, role.Action, role.AccessType, role.Cluster, role.Namespace, role.Group, role.Kind, role.Resource, role.Workflow) + return key +} diff --git a/wire_gen.go b/wire_gen.go index 9346e047634..f4d42671692 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -706,7 +706,7 @@ func InitializeApp() (*App, error) { teamRestHandlerImpl := team2.NewTeamRestHandlerImpl(sugaredLogger, teamServiceImpl, userServiceImpl, enforcerImpl, validate, userAuthServiceImpl, deleteServiceExtendedImpl) teamRouterImpl := team2.NewTeamRouterImpl(teamRestHandlerImpl) roleGroupServiceImpl := user.NewRoleGroupServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, userCommonServiceImpl) - userRestHandlerImpl := user2.NewUserRestHandlerImpl(userServiceImpl, validate, sugaredLogger, enforcerImpl, roleGroupServiceImpl, userCommonServiceImpl) + userRestHandlerImpl := user2.NewUserRestHandlerImpl(userServiceImpl, validate, sugaredLogger, enforcerImpl, roleGroupServiceImpl, userCommonServiceImpl, teamServiceImpl) userRouterImpl := user2.NewUserRouterImpl(userRestHandlerImpl) chartRefRestHandlerImpl := restHandler.NewChartRefRestHandlerImpl(sugaredLogger, chartRefServiceImpl, chartServiceImpl) chartRefRouterImpl := router.NewChartRefRouterImpl(chartRefRestHandlerImpl)