diff --git a/Wire.go b/Wire.go index e5c056f9cf..3401d2d623 100644 --- a/Wire.go +++ b/Wire.go @@ -73,6 +73,7 @@ import ( "github.com/devtron-labs/devtron/api/sse" "github.com/devtron-labs/devtron/api/team" "github.com/devtron-labs/devtron/api/terminal" + "github.com/devtron-labs/devtron/api/userResource" util5 "github.com/devtron-labs/devtron/api/util" webhookHelm "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/cel" @@ -170,6 +171,7 @@ import ( workflow3 "github.com/devtron-labs/devtron/pkg/workflow" "github.com/devtron-labs/devtron/pkg/workflow/dag" util2 "github.com/devtron-labs/devtron/util" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" cron2 "github.com/devtron-labs/devtron/util/cron" "github.com/devtron-labs/devtron/util/rbac" "github.com/google/wire" @@ -213,6 +215,7 @@ func InitializeApp() (*App, error) { workflow3.WorkflowWireSet, imageTagging.WireSet, devtronResource.DevtronResourceWireSet, + userResource.UserResourceWireSet, policyGovernance.PolicyGovernanceWireSet, resourceScan.ScanningResultWireSet, @@ -502,6 +505,9 @@ func InitializeApp() (*App, error) { rbac.NewEnforcerUtilImpl, wire.Bind(new(rbac.EnforcerUtil), new(*rbac.EnforcerUtilImpl)), + commonEnforcementFunctionsUtil.NewCommonEnforcementUtilImpl, + wire.Bind(new(commonEnforcementFunctionsUtil.CommonEnforcementUtil), new(*commonEnforcementFunctionsUtil.CommonEnforcementUtilImpl)), + chartConfig.NewPipelineConfigRepository, wire.Bind(new(chartConfig.PipelineConfigRepository), new(*chartConfig.PipelineConfigRepositoryImpl)), diff --git a/api/appStore/chartGroup/ChartGroupRestHandler.go b/api/appStore/chartGroup/ChartGroupRestHandler.go index 3f0bf44847..199ce00bec 100644 --- a/api/appStore/chartGroup/ChartGroupRestHandler.go +++ b/api/appStore/chartGroup/ChartGroupRestHandler.go @@ -274,7 +274,7 @@ func (impl *ChartGroupRestHandlerImpl) GetChartGroupList(w http.ResponseWriter, return } } - res, err := impl.ChartGroupService.ChartGroupList(maxCount) + res, err := impl.ChartGroupService.GetChartGroupList(maxCount) if err != nil { impl.Logger.Errorw("service err, GetChartGroupList", "err", err, "max", max) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) diff --git a/api/auth/sso/SsoLoginHandler.go b/api/auth/sso/SsoLoginHandler.go index 02827c558d..6044df9995 100644 --- a/api/auth/sso/SsoLoginHandler.go +++ b/api/auth/sso/SsoLoginHandler.go @@ -19,10 +19,10 @@ package sso import ( "encoding/json" "errors" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "net/http" "strconv" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" "github.com/devtron-labs/devtron/pkg/auth/sso" diff --git a/api/auth/user/UserAuthHandler.go b/api/auth/user/UserAuthHandler.go index 4187adf7be..56c575e7ca 100644 --- a/api/auth/user/UserAuthHandler.go +++ b/api/auth/user/UserAuthHandler.go @@ -19,6 +19,8 @@ package user import ( "encoding/json" "fmt" + bean2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + bean3 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "net/http" "strings" @@ -146,7 +148,7 @@ func (handler UserAuthHandlerImpl) AddDefaultPolicyAndRoles(w http.ResponseWrite //for START in Casbin Object Ends Here //loading policy for safety casbin.LoadPolicy() - var policies []casbin.Policy + var policies []bean2.Policy var policiesAdmin bean.PolicyRequest err := json.Unmarshal([]byte(adminPolicies), &policiesAdmin) if err != nil { @@ -193,7 +195,7 @@ func (handler UserAuthHandlerImpl) AddDefaultPolicyAndRoles(w http.ResponseWrite roleView = strings.ReplaceAll(roleView, "", env) roleView = strings.ReplaceAll(roleView, "", app) - var roleAdminData bean.RoleData + var roleAdminData bean3.RoleData err = json.Unmarshal([]byte(roleAdmin), &roleAdminData) if err != nil { handler.logger.Errorw("request err, AddDefaultPolicyAndRoles", "err", err, "payload", roleAdminData) @@ -207,7 +209,7 @@ func (handler UserAuthHandlerImpl) AddDefaultPolicyAndRoles(w http.ResponseWrite return } - var roleTriggerData bean.RoleData + var roleTriggerData bean3.RoleData err = json.Unmarshal([]byte(roleTrigger), &roleTriggerData) if err != nil { handler.logger.Errorw("request err, AddDefaultPolicyAndRoles", "err", err, "payload", roleTriggerData) @@ -221,7 +223,7 @@ func (handler UserAuthHandlerImpl) AddDefaultPolicyAndRoles(w http.ResponseWrite return } - var roleViewData bean.RoleData + var roleViewData bean3.RoleData err = json.Unmarshal([]byte(roleView), &roleViewData) if err != nil { handler.logger.Errorw("request err, AddDefaultPolicyAndRoles", "err", err, "payload", roleViewData) diff --git a/api/auth/user/UserRestHandler.go b/api/auth/user/UserRestHandler.go index fda5f0f23b..9c7610f310 100644 --- a/api/auth/user/UserRestHandler.go +++ b/api/auth/user/UserRestHandler.go @@ -21,14 +21,12 @@ import ( "errors" 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" - "github.com/go-pg/pg" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/gorilla/schema" "net/http" "strconv" "strings" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/restHandler/common" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -71,24 +69,27 @@ type userNamePassword struct { } type UserRestHandlerImpl struct { - userService user2.UserService - validator *validator.Validate - logger *zap.SugaredLogger - enforcer casbin.Enforcer - roleGroupService user2.RoleGroupService - userCommonService user2.UserCommonService + userService user2.UserService + validator *validator.Validate + logger *zap.SugaredLogger + enforcer casbin.Enforcer + roleGroupService user2.RoleGroupService + userCommonService user2.UserCommonService + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil } func NewUserRestHandlerImpl(userService user2.UserService, validator *validator.Validate, logger *zap.SugaredLogger, enforcer casbin.Enforcer, roleGroupService user2.RoleGroupService, - userCommonService user2.UserCommonService) *UserRestHandlerImpl { + userCommonService user2.UserCommonService, + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil) *UserRestHandlerImpl { userAuthHandler := &UserRestHandlerImpl{ - userService: userService, - validator: validator, - logger: logger, - enforcer: enforcer, - roleGroupService: roleGroupService, - userCommonService: userCommonService, + userService: userService, + validator: validator, + logger: logger, + enforcer: enforcer, + roleGroupService: roleGroupService, + userCommonService: userCommonService, + rbacEnforcementUtil: rbacEnforcementUtil, } return userAuthHandler } @@ -100,7 +101,7 @@ func (handler UserRestHandlerImpl) CreateUser(w http.ResponseWriter, r *http.Req common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } - var userInfo bean.UserInfo + var userInfo bean2.UserInfo err = decoder.Decode(&userInfo) if err != nil { handler.logger.Errorw("request err, CreateUser", "err", err, "payload", userInfo) @@ -164,7 +165,7 @@ func (handler UserRestHandlerImpl) UpdateUser(w http.ResponseWriter, r *http.Req common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } - var userInfo bean.UserInfo + var userInfo bean2.UserInfo err = decoder.Decode(&userInfo) if err != nil { handler.logger.Errorw("request err, UpdateUser", "err", err, "payload", userInfo) @@ -199,22 +200,6 @@ func (handler UserRestHandlerImpl) UpdateUser(w http.ResponseWriter, r *http.Req return } common.WriteJsonResp(w, err, res, http.StatusOK) - //if len(restrictedGroups) == 0 { - // common.WriteJsonResp(w, err, res, http.StatusOK) - //} else { - // errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin := helper.CreateErrorMessageForUserRoleGroups(restrictedGroups) - // - // if rolesChanged || groupsModified { - // // warning - // message := fmt.Errorf("User permissions updated partially. %s%s", errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin) - // common.WriteJsonResp(w, message, nil, http.StatusExpectationFailed) - // - // } else { - // //error - // message := fmt.Errorf("Permission could not be added/removed. %s%s", errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin) - // common.WriteJsonResp(w, message, nil, http.StatusBadRequest) - // } - //} } func (handler UserRestHandlerImpl) GetById(w http.ResponseWriter, r *http.Request) { @@ -231,7 +216,7 @@ func (handler UserRestHandlerImpl) GetById(w http.ResponseWriter, r *http.Reques common.WriteJsonResp(w, err, nil, http.StatusBadRequest) return } - res, err := handler.userService.GetById(int32(id)) + res, err := handler.userService.GetByIdWithoutGroupClaims(int32(id)) if err != nil { handler.logger.Errorw("service err, GetById", "err", err, "id", id) common.WriteJsonResp(w, err, "Failed to get by id", http.StatusInternalServerError) @@ -241,24 +226,7 @@ func (handler UserRestHandlerImpl) GetById(w http.ResponseWriter, r *http.Reques token := r.Header.Get("token") // NOTE: if no role assigned, user will be visible to all manager. // RBAC enforcer applying - filteredRoleFilter := make([]bean.RoleFilter, 0) - if res.RoleFilters != nil && len(res.RoleFilters) > 0 { - isUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - for _, filter := range res.RoleFilters { - authPass := handler.checkRbacForFilter(token, filter, isUserSuperAdmin) - if authPass { - filteredRoleFilter = append(filteredRoleFilter, filter) - } - } - } - for index, roleFilter := range filteredRoleFilter { - if roleFilter.Entity == "" { - filteredRoleFilter[index].Entity = bean2.ENTITY_APPS - if roleFilter.AccessType == "" { - filteredRoleFilter[index].AccessType = bean2.DEVTRON_APP - } - } - } + filteredRoleFilter := handler.GetFilteredRoleFiltersAccordingToAccess(token, res.RoleFilters) res.RoleFilters = filteredRoleFilter //RBAC enforcer Ends @@ -276,51 +244,17 @@ func (handler UserRestHandlerImpl) GetAllV2(w http.ResponseWriter, r *http.Reque // RBAC enforcer applying token := r.Header.Get("token") //checking superAdmin access - isAuthorised := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if !isAuthorised { - user, err := handler.userService.GetById(userId) - if err != nil { - handler.logger.Errorw("error in getting user by id", "err", err) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - var roleFilters []bean.RoleFilter - if len(user.UserRoleGroup) > 0 { - groupRoleFilters, err := handler.userService.GetRoleFiltersByUserRoleGroups(user.UserRoleGroup) - if err != nil { - handler.logger.Errorw("Error in getting role filters by group names", "err", err, "UserRoleGroup", user.UserRoleGroup) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - if len(groupRoleFilters) > 0 { - roleFilters = append(roleFilters, groupRoleFilters...) - } - } - if user.RoleFilters != nil && len(user.RoleFilters) > 0 { - roleFilters = append(roleFilters, user.RoleFilters...) - } - if len(roleFilters) > 0 { - for _, filter := range roleFilters { - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); ok { - isAuthorised = true - break - } - } - 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 { - isAuthorised = true - break - } - } - } - } + isAuthorised, err := handler.rbacEnforcementUtil.CheckRbacForMangerAndAboveAccess(token, userId) + if err != nil { + handler.logger.Errorw("err, CheckRbacForMangerAndAboveAccess", "err", err) + common.WriteJsonResp(w, err, "Failed to Get", http.StatusInternalServerError) + return } if !isAuthorised { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } - req := &bean.ListingRequest{} + req := &bean2.ListingRequest{} err = decoder.Decode(req, r.URL.Query()) if err != nil { handler.logger.Errorw("request err, GetAll", "err", err, "payload", req) @@ -346,46 +280,11 @@ func (handler UserRestHandlerImpl) GetAll(w http.ResponseWriter, r *http.Request // RBAC enforcer applying token := r.Header.Get("token") - //checking superAdmin access - isAuthorised := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if !isAuthorised { - user, err := handler.userService.GetById(userId) - if err != nil { - handler.logger.Errorw("error in getting user by id", "err", err) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - var roleFilters []bean.RoleFilter - if len(user.UserRoleGroup) > 0 { - groupRoleFilters, err := handler.userService.GetRoleFiltersByUserRoleGroups(user.UserRoleGroup) - if err != nil { - handler.logger.Errorw("Error in getting role filters by group names", "err", err, "UserRoleGroup", user.UserRoleGroup) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - if len(groupRoleFilters) > 0 { - roleFilters = append(roleFilters, groupRoleFilters...) - } - } - if user.RoleFilters != nil && len(user.RoleFilters) > 0 { - roleFilters = append(roleFilters, user.RoleFilters...) - } - if len(roleFilters) > 0 { - for _, filter := range roleFilters { - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); ok { - isAuthorised = true - break - } - } - 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 { - isAuthorised = true - break - } - } - } - } + isAuthorised, err := handler.rbacEnforcementUtil.CheckRbacForMangerAndAboveAccess(token, userId) + if err != nil { + handler.logger.Errorw("err, CheckRbacForMangerAndAboveAccess", "err", err) + common.WriteJsonResp(w, err, "Failed to Get", http.StatusInternalServerError) + return } if !isAuthorised { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) @@ -441,7 +340,7 @@ func (handler UserRestHandlerImpl) DeleteUser(w http.ResponseWriter, r *http.Req return } handler.logger.Infow("request payload, DeleteUser", "err", err, "id", id) - user, err := handler.userService.GetById(int32(id)) + user, err := handler.userService.GetByIdWithoutGroupClaims(int32(id)) if err != nil { common.WriteJsonResp(w, err, "", http.StatusInternalServerError) return @@ -449,34 +348,10 @@ func (handler UserRestHandlerImpl) DeleteUser(w http.ResponseWriter, r *http.Req // RBAC enforcer applying token := r.Header.Get("token") - isActionUserSuperAdmin := false - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); ok { - isActionUserSuperAdmin = true - } - if user.RoleFilters != nil && len(user.RoleFilters) > 0 { - for _, filter := range user.RoleFilters { - if filter.AccessType == bean2.APP_ACCESS_TYPE_HELM && !isActionUserSuperAdmin { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionDelete, filter.Team); !ok { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } - } - 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 { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } - } - } - } else { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionDelete, ""); !ok { - common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) - return - } + isAuthorised := handler.CheckRbacForUserDelete(token, user) + if !isAuthorised { + common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) + return } //RBAC enforcer Ends //validation @@ -506,7 +381,7 @@ func (handler UserRestHandlerImpl) BulkDeleteUsers(w http.ResponseWriter, r *htt } decoder := json.NewDecoder(r.Body) // request decoding - var request *bean.BulkDeleteRequest + var request *bean2.BulkDeleteRequest err = decoder.Decode(&request) if err != nil { handler.logger.Errorw("request err, BulkDeleteUsers", "payload", request, "err", err) @@ -567,24 +442,7 @@ func (handler UserRestHandlerImpl) FetchRoleGroupById(w http.ResponseWriter, r * // NOTE: if no role assigned, user will be visible to all manager. // RBAC enforcer applying token := r.Header.Get("token") - filteredRoleFilter := make([]bean.RoleFilter, 0) - if res.RoleFilters != nil && len(res.RoleFilters) > 0 { - isUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - for _, filter := range res.RoleFilters { - authPass := handler.checkRbacForFilter(token, filter, isUserSuperAdmin) - if authPass { - filteredRoleFilter = append(filteredRoleFilter, filter) - } - } - } - for index, roleFilter := range filteredRoleFilter { - if roleFilter.Entity == "" { - filteredRoleFilter[index].Entity = bean2.ENTITY_APPS - } - if roleFilter.Entity == bean2.ENTITY_APPS && roleFilter.AccessType == "" { - filteredRoleFilter[index].AccessType = bean2.DEVTRON_APP - } - } + filteredRoleFilter := handler.GetFilteredRoleFiltersAccordingToAccess(token, res.RoleFilters) res.RoleFilters = filteredRoleFilter //RBAC enforcer Ends @@ -592,35 +450,6 @@ func (handler UserRestHandlerImpl) FetchRoleGroupById(w http.ResponseWriter, r * common.WriteJsonResp(w, err, res, http.StatusOK) } -func (handler UserRestHandlerImpl) checkRbacForFilter(token string, filter bean.RoleFilter, isUserSuperAdmin bool) bool { - isAuthorised := true - switch { - case isUserSuperAdmin: - isAuthorised = true - case filter.AccessType == bean2.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { - isAuthorised = false - } - - case len(filter.Team) > 0: - // this is case of devtron app - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); !ok { - isAuthorised = false - } - - case filter.Entity == bean.CLUSTER_ENTITIY: - isValidAuth := handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) - if !isValidAuth { - isAuthorised = false - } - case filter.Entity == bean.CHART_GROUP_ENTITY: - isAuthorised = true - default: - isAuthorised = false - } - return isAuthorised -} - func (handler UserRestHandlerImpl) CreateRoleGroup(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) userId, err := handler.userService.GetLoggedInUser(r) @@ -628,7 +457,7 @@ func (handler UserRestHandlerImpl) CreateRoleGroup(w http.ResponseWriter, r *htt common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } - var request bean.RoleGroup + var request bean2.RoleGroup err = decoder.Decode(&request) if err != nil { handler.logger.Errorw("request err, CreateRoleGroup", "err", err, "payload", request) @@ -681,7 +510,7 @@ func (handler UserRestHandlerImpl) UpdateRoleGroup(w http.ResponseWriter, r *htt common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } - var request bean.RoleGroup + var request bean2.RoleGroup err = decoder.Decode(&request) if err != nil { handler.logger.Errorw("request err, UpdateRoleGroup", "err", err, "payload", request) @@ -729,53 +558,18 @@ func (handler UserRestHandlerImpl) FetchRoleGroupsV2(w http.ResponseWriter, r *h // RBAC enforcer applying token := r.Header.Get("token") //checking superAdmin access - isAuthorised := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if !isAuthorised { - user, err := handler.userService.GetById(userId) - if err != nil { - handler.logger.Errorw("error in getting user by id", "err", err) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - var roleFilters []bean.RoleFilter - if len(user.UserRoleGroup) > 0 { - groupRoleFilters, err := handler.userService.GetRoleFiltersByUserRoleGroups(user.UserRoleGroup) - if err != nil { - handler.logger.Errorw("Error in getting role filters by group names", "err", err, "UserRoleGroup", user.UserRoleGroup) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - if len(groupRoleFilters) > 0 { - roleFilters = append(roleFilters, groupRoleFilters...) - } - } - if user.RoleFilters != nil && len(user.RoleFilters) > 0 { - roleFilters = append(roleFilters, user.RoleFilters...) - } - if len(roleFilters) > 0 { - for _, filter := range roleFilters { - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); ok { - isAuthorised = true - break - } - } - 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 { - isAuthorised = true - break - } - } - - } - } + isAuthorised, err := handler.rbacEnforcementUtil.CheckRbacForMangerAndAboveAccess(token, userId) + if err != nil { + handler.logger.Errorw("err, CheckRbacForMangerAndAboveAccess", "err", err) + common.WriteJsonResp(w, err, "Failed to Get", http.StatusInternalServerError) + return } if !isAuthorised { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } - req := &bean.ListingRequest{} + req := &bean2.ListingRequest{} err = decoder.Decode(req, r.URL.Query()) if err != nil { handler.logger.Errorw("request err, FetchRoleGroups", "err", err, "payload", req) @@ -799,47 +593,11 @@ func (handler UserRestHandlerImpl) FetchRoleGroups(w http.ResponseWriter, r *htt } // RBAC enforcer applying token := r.Header.Get("token") - //checking superAdmin access - isAuthorised := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if !isAuthorised { - user, err := handler.userService.GetById(userId) - if err != nil { - handler.logger.Errorw("error in getting user by id", "err", err) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - var roleFilters []bean.RoleFilter - if len(user.UserRoleGroup) > 0 { - groupRoleFilters, err := handler.userService.GetRoleFiltersByUserRoleGroups(user.UserRoleGroup) - if err != nil { - handler.logger.Errorw("Error in getting role filters by group names", "err", err, "UserRoleGroup", user.UserRoleGroup) - common.WriteJsonResp(w, err, "", http.StatusInternalServerError) - return - } - if len(groupRoleFilters) > 0 { - roleFilters = append(roleFilters, groupRoleFilters...) - } - } - if user.RoleFilters != nil && len(user.RoleFilters) > 0 { - roleFilters = append(roleFilters, user.RoleFilters...) - } - if len(roleFilters) > 0 { - for _, filter := range roleFilters { - if len(filter.Team) > 0 { - if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); ok { - isAuthorised = true - break - } - } - 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 { - isAuthorised = true - break - } - } - - } - } + isAuthorised, err := handler.rbacEnforcementUtil.CheckRbacForMangerAndAboveAccess(token, userId) + if err != nil { + handler.logger.Errorw("err, CheckRbacForMangerAndAboveAccess", "err", err) + common.WriteJsonResp(w, err, "Failed to Get", http.StatusInternalServerError) + return } if !isAuthorised { common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) @@ -869,7 +627,7 @@ func (handler UserRestHandlerImpl) FetchDetailedRoleGroups(w http.ResponseWriter common.WriteJsonResp(w, errors.New("unauthorized"), nil, http.StatusForbidden) return } - req := &bean.ListingRequest{ShowAll: true} + req := &bean2.ListingRequest{ShowAll: true} res, err := handler.roleGroupService.FetchDetailedRoleGroups(req) if err != nil { handler.logger.Errorw("service err, FetchRoleGroups", "err", err) @@ -948,7 +706,7 @@ func (handler UserRestHandlerImpl) BulkDeleteRoleGroups(w http.ResponseWriter, r } decoder := json.NewDecoder(r.Body) // request decoding - var request *bean.BulkDeleteRequest + var request *bean2.BulkDeleteRequest err = decoder.Decode(&request) if err != nil { handler.logger.Errorw("request err, BulkDeleteRoleGroups", "payload", request, "err", err) @@ -990,7 +748,7 @@ func (handler UserRestHandlerImpl) CheckUserRoles(w http.ResponseWriter, r *http common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) return } - roles, err := handler.userService.CheckUserRoles(userId) + roles, err := handler.userService.CheckUserRoles(userId, "") if err != nil { handler.logger.Errorw("service err, CheckUserRoles", "err", err, "userId", userId) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) @@ -1004,7 +762,7 @@ func (handler UserRestHandlerImpl) CheckUserRoles(w http.ResponseWriter, r *http result := make(map[string]interface{}) var isSuperAdmin, isAdmin, isManager, isTrigger bool for _, role := range roles { - if role == bean.SUPERADMIN { + if role == bean2.SUPERADMIN { isSuperAdmin = true break } @@ -1036,7 +794,7 @@ func (handler UserRestHandlerImpl) CheckUserRoles(w http.ResponseWriter, r *http result["roles"] = roles result["superAdmin"] = false for _, item := range roles { - if item == bean.SUPERADMIN { + if item == bean2.SUPERADMIN { result["superAdmin"] = true } } @@ -1123,242 +881,3 @@ func (handler UserRestHandlerImpl) CheckManagerAuth(resource, token string, obje return true } - -func (handler UserRestHandlerImpl) checkRBACForUserCreate(token string, requestSuperAdmin bool, roleFilters []bean.RoleFilter, - roleGroups []bean.UserRoleGroup) (isAuthorised bool, err error) { - isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if requestSuperAdmin && !isActionUserSuperAdmin { - return false, nil - } - isAuthorised = isActionUserSuperAdmin - 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 && 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 - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - return false, nil - } - } - } - 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 - } - 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 && 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 - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - return false, nil - } - } - } else { - isAuthorised = false - } - } - } - return isAuthorised, nil -} - -func (handler UserRestHandlerImpl) checkRBACForUserUpdate(token string, userInfo *bean.UserInfo, isUserAlreadySuperAdmin bool, eliminatedRoleFilters, - eliminatedGroupRoles []*repository.RoleModel, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error) { - isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - requestSuperAdmin := userInfo.SuperAdmin - if (requestSuperAdmin || isUserAlreadySuperAdmin) && !isActionUserSuperAdmin { - //if user is going to be provided with super-admin access or already a super-admin then the action user should be a super-admin - return false, nil - } - roleFilters := userInfo.RoleFilters - roleGroups := userInfo.UserRoleGroup - isAuthorised = isActionUserSuperAdmin - eliminatedRolesToBeChecked := append(eliminatedRoleFilters, eliminatedGroupRoles...) - 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: - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - return false, nil - } - } - } - 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 - } - if !isAuthorised { - return false, nil - } - } - } - if len(roleGroups) > 0 { // auth check inside groups - //filter out roleGroups (existing has to be ignore while checking rbac) - filteredRoleGroups := util2.FilterRoleGroupIfAlreadyPresent(roleGroups, mapOfExistingUserRoleGroup) - if len(filteredRoleGroups) > 0 { - groupRoles, err := handler.roleGroupService.FetchRolesForUserRoleGroups(roleGroups) - if err != nil && err != pg.ErrNoRows { - handler.logger.Errorw("service err, UpdateUser", "err", err, "filteredRoleGroups", filteredRoleGroups) - return false, err - } - 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 { - return false, nil - } - } - } else { - isAuthorised = false - } - } - } - } - return isAuthorised, nil -} - -func (handler UserRestHandlerImpl) checkRBACForRoleGroupUpdate(token string, groupInfo *bean.RoleGroup, eliminatedRoleFilters []*repository.RoleModel, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error) { - isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - requestSuperAdmin := groupInfo.SuperAdmin - if (requestSuperAdmin || isRoleGroupAlreadySuperAdmin) && !isActionUserSuperAdmin { - //if user is going to be provided with super-admin access or already a super-admin then the action user should be a super-admin - return false, nil - } - isAuthorised = isActionUserSuperAdmin - 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: - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - return false, nil - } - } - } - 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 - } - if !isAuthorised { - return false, nil - } - } - } - } - return isAuthorised, nil -} - -func (handler UserRestHandlerImpl) checkRBACForRoleGroupDelete(token string, userGroup *bean.RoleGroup) (isAuthorised bool, err error) { - isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - if userGroup.SuperAdmin && !isActionUserSuperAdmin { - return false, nil - } - isAuthorised = isActionUserSuperAdmin - if !isAuthorised { - if userGroup.RoleFilters != nil && len(userGroup.RoleFilters) > 0 { //auth check inside roleFilters - for _, filter := range userGroup.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: - isAuthorised = true - default: - isAuthorised = false - } - if !isAuthorised { - return false, nil - } - } - } - } - return isAuthorised, nil -} diff --git a/api/auth/user/UserRestHandler_ent.go b/api/auth/user/UserRestHandler_ent.go new file mode 100644 index 0000000000..cd72152277 --- /dev/null +++ b/api/auth/user/UserRestHandler_ent.go @@ -0,0 +1,333 @@ +package user + +import ( + util2 "github.com/devtron-labs/devtron/api/auth/user/util" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/repository" + "github.com/go-pg/pg" +) + +func (handler UserRestHandlerImpl) checkRBACForUserCreate(token string, requestSuperAdmin bool, roleFilters []bean2.RoleFilter, + roleGroups []bean2.UserRoleGroup) (isAuthorised bool, err error) { + isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + if requestSuperAdmin && !isActionUserSuperAdmin { + return false, nil + } + isAuthorised = isActionUserSuperAdmin + if !isAuthorised { + if roleFilters != nil && len(roleFilters) > 0 { //auth check inside roleFilters + for _, filter := range roleFilters { + switch { + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.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 == bean2.CHART_GROUP_ENTITY && len(roleFilters) > 1: //if entities apart from chartGroup entity are present, not checking chartGroup access + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + 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 + } + if len(groupRoles) > 0 { + for _, groupRole := range groupRoles { + switch { + case groupRole.Action == bean2.ACTION_SUPERADMIN: + isAuthorised = isActionUserSuperAdmin + case groupRole.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(groupRole.Cluster, groupRole.Namespace, groupRole.Group, groupRole.Kind, groupRole.Resource, token, handler.CheckManagerAuth) + case groupRole.Entity == bean2.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 == bean2.CHART_GROUP_ENTITY && len(groupRoles) > 1: //if entities apart from chartGroup entity are present, not checking chartGroup access + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } else { + isAuthorised = false + } + } + } + return isAuthorised, nil +} + +func (handler UserRestHandlerImpl) checkRBACForUserUpdate(token string, userInfo *bean2.UserInfo, isUserAlreadySuperAdmin bool, eliminatedRoleFilters, + eliminatedGroupRoles []*repository.RoleModel, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error) { + isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + requestSuperAdmin := userInfo.SuperAdmin + if (requestSuperAdmin || isUserAlreadySuperAdmin) && !isActionUserSuperAdmin { + //if user is going to be provided with super-admin access or already a super-admin then the action user should be a super-admin + return false, nil + } + roleFilters := userInfo.RoleFilters + roleGroups := userInfo.UserRoleGroup + isAuthorised = isActionUserSuperAdmin + eliminatedRolesToBeChecked := append(eliminatedRoleFilters, eliminatedGroupRoles...) + if !isAuthorised { + if roleFilters != nil && len(roleFilters) > 0 { //auth check inside roleFilters + for _, filter := range roleFilters { + switch { + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + if eliminatedRolesToBeChecked != nil && len(eliminatedRolesToBeChecked) > 0 { + for _, filter := range eliminatedRolesToBeChecked { + switch { + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + if len(roleGroups) > 0 { // auth check inside groups + //filter out roleGroups (existing has to be ignore while checking rbac) + filteredRoleGroups := util2.FilterRoleGroupIfAlreadyPresent(roleGroups, mapOfExistingUserRoleGroup) + if len(filteredRoleGroups) > 0 { + groupRoles, err := handler.roleGroupService.FetchRolesForUserRoleGroups(roleGroups) + if err != nil && err != pg.ErrNoRows { + handler.logger.Errorw("service err, UpdateUser", "err", err, "filteredRoleGroups", filteredRoleGroups) + return false, err + } + if len(groupRoles) > 0 { + for _, groupRole := range groupRoles { + switch { + case groupRole.Action == bean2.ACTION_SUPERADMIN: + isAuthorised = isActionUserSuperAdmin + case groupRole.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(groupRole.Cluster, groupRole.Namespace, groupRole.Group, groupRole.Kind, groupRole.Resource, token, handler.CheckManagerAuth) + case groupRole.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } else { + isAuthorised = false + } + } + } + } + return isAuthorised, nil +} + +func (handler UserRestHandlerImpl) CheckRbacForUserDelete(token string, user *bean2.UserInfo) (isAuthorised bool) { + isActionUserSuperAdmin := false + if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); ok { + isActionUserSuperAdmin = true + } + isAuthorised = isActionUserSuperAdmin + if !isAuthorised { + if user.RoleFilters != nil && len(user.RoleFilters) > 0 { + for _, filter := range user.RoleFilters { + if filter.AccessType == bean2.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs { + // helm apps, jobs are managed by super admin + return false + } + if len(filter.Team) > 0 { + if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionDelete, filter.Team); !ok { + return 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 { + return false + } + } + } + } else { + if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionDelete, ""); !ok { + return false + } + } + isAuthorised = true + } + return isAuthorised +} + +func (handler UserRestHandlerImpl) checkRBACForRoleGroupUpdate(token string, groupInfo *bean2.RoleGroup, eliminatedRoleFilters []*repository.RoleModel, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error) { + isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + requestSuperAdmin := groupInfo.SuperAdmin + if (requestSuperAdmin || isRoleGroupAlreadySuperAdmin) && !isActionUserSuperAdmin { + //if user is going to be provided with super-admin access or already a super-admin then the action user should be a super-admin + return false, nil + } + isAuthorised = isActionUserSuperAdmin + if !isAuthorised { + if groupInfo.RoleFilters != nil && len(groupInfo.RoleFilters) > 0 { //auth check inside roleFilters + for _, filter := range groupInfo.RoleFilters { + switch { + case filter.Action == bean2.ACTION_SUPERADMIN: + isAuthorised = isActionUserSuperAdmin + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + if len(eliminatedRoleFilters) > 0 { + for _, filter := range eliminatedRoleFilters { + switch { + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + } + return isAuthorised, nil +} + +func (handler UserRestHandlerImpl) checkRBACForRoleGroupDelete(token string, userGroup *bean2.RoleGroup) (isAuthorised bool, err error) { + isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + if userGroup.SuperAdmin && !isActionUserSuperAdmin { + return false, nil + } + isAuthorised = isActionUserSuperAdmin + if !isAuthorised { + if userGroup.RoleFilters != nil && len(userGroup.RoleFilters) > 0 { //auth check inside roleFilters + for _, filter := range userGroup.RoleFilters { + switch { + case filter.Action == bean2.ACTION_SUPERADMIN: + isAuthorised = isActionUserSuperAdmin + case filter.AccessType == bean2.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 == bean2.CLUSTER_ENTITIY: + isAuthorised = handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + if !isAuthorised { + return false, nil + } + } + } + } + return isAuthorised, nil +} + +func (handler UserRestHandlerImpl) GetFilteredRoleFiltersAccordingToAccess(token string, roleFilters []bean2.RoleFilter) []bean2.RoleFilter { + filteredRoleFilter := make([]bean2.RoleFilter, 0) + if len(roleFilters) > 0 { + isUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + for _, filter := range roleFilters { + authPass := handler.checkRbacForFilter(token, filter, isUserSuperAdmin) + if authPass { + filteredRoleFilter = append(filteredRoleFilter, filter) + } + } + } + for index, roleFilter := range filteredRoleFilter { + if roleFilter.Entity == "" { + filteredRoleFilter[index].Entity = bean2.ENTITY_APPS + if roleFilter.AccessType == "" { + filteredRoleFilter[index].AccessType = bean2.DEVTRON_APP + } + } + } + return filteredRoleFilter +} + +func (handler UserRestHandlerImpl) checkRbacForFilter(token string, filter bean2.RoleFilter, isUserSuperAdmin bool) bool { + isAuthorised := true + switch { + case isUserSuperAdmin: + isAuthorised = true + case filter.AccessType == bean2.APP_ACCESS_TYPE_HELM || filter.Entity == bean2.EntityJobs: + if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*"); !ok { + isAuthorised = false + } + + case len(filter.Team) > 0: + // this is case of devtron app + if ok := handler.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); !ok { + isAuthorised = false + } + + case filter.Entity == bean2.CLUSTER_ENTITIY: + isValidAuth := handler.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, handler.CheckManagerAuth) + if !isValidAuth { + isAuthorised = false + } + case filter.Entity == bean2.CHART_GROUP_ENTITY: + isAuthorised = true + default: + isAuthorised = false + } + return isAuthorised +} diff --git a/api/auth/user/UserRouter.go b/api/auth/user/UserRouter.go index edfefe6434..6197d793ec 100644 --- a/api/auth/user/UserRouter.go +++ b/api/auth/user/UserRouter.go @@ -41,12 +41,18 @@ func (router UserRouterImpl) InitUserRouter(userAuthRouter *mux.Router) { HandlerFunc(router.userRestHandler.GetAllV2).Methods("GET") userAuthRouter.Path("/{id}"). HandlerFunc(router.userRestHandler.GetById).Methods("GET") + userAuthRouter.Path("/v2/{id}"). + HandlerFunc(router.userRestHandler.GetById).Methods("GET") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path(""). HandlerFunc(router.userRestHandler.CreateUser).Methods("POST") + userAuthRouter.Path("/v2"). + HandlerFunc(router.userRestHandler.CreateUser).Methods("POST") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path(""). HandlerFunc(router.userRestHandler.GetAll).Methods("GET") userAuthRouter.Path(""). HandlerFunc(router.userRestHandler.UpdateUser).Methods("PUT") + userAuthRouter.Path("/v2"). + HandlerFunc(router.userRestHandler.UpdateUser).Methods("PUT") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path("/bulk"). HandlerFunc(router.userRestHandler.BulkDeleteUsers).Methods("DELETE") userAuthRouter.Path("/{id}"). @@ -58,10 +64,16 @@ func (router UserRouterImpl) InitUserRouter(userAuthRouter *mux.Router) { HandlerFunc(router.userRestHandler.FetchRoleGroupsV2).Methods("GET") userAuthRouter.Path("/role/group/{id}"). HandlerFunc(router.userRestHandler.FetchRoleGroupById).Methods("GET") + userAuthRouter.Path("/role/group/v2/{id}"). + HandlerFunc(router.userRestHandler.FetchRoleGroupById).Methods("GET") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path("/role/group"). HandlerFunc(router.userRestHandler.CreateRoleGroup).Methods("POST") + userAuthRouter.Path("/role/group/v2"). + HandlerFunc(router.userRestHandler.CreateRoleGroup).Methods("POST") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path("/role/group"). HandlerFunc(router.userRestHandler.UpdateRoleGroup).Methods("PUT") + userAuthRouter.Path("/role/group/v2"). + HandlerFunc(router.userRestHandler.UpdateRoleGroup).Methods("PUT") // v2 apis are opened for consistent dashboard handling, internally V1 methods are used userAuthRouter.Path("/role/group"). HandlerFunc(router.userRestHandler.FetchRoleGroups).Methods("GET") userAuthRouter.Path("/role/group/detailed/get"). diff --git a/api/auth/user/util/util.go b/api/auth/user/util/util.go index d4fbad25c8..8ee5840473 100644 --- a/api/auth/user/util/util.go +++ b/api/auth/user/util/util.go @@ -17,7 +17,7 @@ package util import ( - "github.com/devtron-labs/devtron/api/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/helper" ) diff --git a/api/bean/PolicyRequest.go b/api/bean/PolicyRequest.go index feba6ab571..487fe64d8e 100644 --- a/api/bean/PolicyRequest.go +++ b/api/bean/PolicyRequest.go @@ -17,9 +17,9 @@ package bean import ( - "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + bean2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" ) type PolicyRequest struct { - Data []casbin.Policy + Data []bean2.Policy } diff --git a/api/cluster/EnvironmentRestHandler.go b/api/cluster/EnvironmentRestHandler.go index 558062123b..146def3a45 100644 --- a/api/cluster/EnvironmentRestHandler.go +++ b/api/cluster/EnvironmentRestHandler.go @@ -23,6 +23,7 @@ import ( request "github.com/devtron-labs/devtron/pkg/cluster/environment" bean2 "github.com/devtron-labs/devtron/pkg/cluster/environment/bean" "github.com/devtron-labs/devtron/pkg/cluster/environment/read" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "net/http" "strconv" "strings" @@ -72,6 +73,7 @@ type EnvironmentRestHandlerImpl struct { deleteService delete2.DeleteService k8sUtil *k8s2.K8sServiceImpl cfg *bean.Config + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil } type ClusterReachableResponse struct { @@ -79,7 +81,8 @@ type ClusterReachableResponse struct { ClusterName string `json:"clusterName"` } -func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, environmentReadService read.EnvironmentReadService, logger *zap.SugaredLogger, userService user.UserService, validator *validator.Validate, enforcer casbin.Enforcer, deleteService delete2.DeleteService, k8sUtil *k8s2.K8sServiceImpl, k8sCommonService k8s.K8sCommonService) *EnvironmentRestHandlerImpl { +func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, environmentReadService read.EnvironmentReadService, logger *zap.SugaredLogger, userService user.UserService, validator *validator.Validate, enforcer casbin.Enforcer, deleteService delete2.DeleteService, k8sUtil *k8s2.K8sServiceImpl, k8sCommonService k8s.K8sCommonService, + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil) *EnvironmentRestHandlerImpl { cfg := &bean.Config{} err := env.Parse(cfg) if err != nil { @@ -98,6 +101,7 @@ func NewEnvironmentRestHandlerImpl(svc request.EnvironmentService, environmentRe cfg: cfg, k8sUtil: k8sUtil, k8sCommonService: k8sCommonService, + rbacEnforcementUtil: rbacEnforcementUtil, } } @@ -318,29 +322,7 @@ func (impl EnvironmentRestHandlerImpl) GetEnvironmentListForAutocomplete(w http. var grantedEnvironment = environments start = time.Now() if !impl.cfg.IgnoreAuthCheck { - grantedEnvironment = make([]bean2.EnvironmentBean, 0) - // RBAC enforcer applying - var envIdentifierList []string - for _, item := range environments { - envIdentifierList = append(envIdentifierList, strings.ToLower(item.EnvironmentIdentifier)) - } - - result := impl.enforcer.EnforceInBatch(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, envIdentifierList) - for _, item := range environments { - - var hasAccess bool - EnvironmentIdentifier := item.ClusterName + "__" + item.Namespace - if item.EnvironmentIdentifier != EnvironmentIdentifier { - // fix for futuristic case - hasAccess = result[strings.ToLower(EnvironmentIdentifier)] || result[strings.ToLower(item.EnvironmentIdentifier)] - } else { - hasAccess = result[strings.ToLower(item.EnvironmentIdentifier)] - } - if hasAccess { - grantedEnvironment = append(grantedEnvironment, item) - } - } - //RBAC enforcer Ends + grantedEnvironment = impl.rbacEnforcementUtil.CheckAuthorisationForEnvs(token, environments) } elapsedTime := time.Since(start) impl.logger.Infow("Env elapsed Time for enforcer", "dbElapsedTime", dbElapsedTime, "elapsedTime", @@ -358,7 +340,7 @@ func (impl EnvironmentRestHandlerImpl) GetCombinedEnvironmentListForDropDown(w h token := r.Header.Get("token") isActionUserSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") - clusters, err := impl.environmentClusterMappingsService.GetCombinedEnvironmentListForDropDown(token, isActionUserSuperAdmin, impl.CheckAuthorizationByEmailInBatchForGlobalEnvironment) + clusters, err := impl.environmentClusterMappingsService.GetCombinedEnvironmentListForDropDown(token, isActionUserSuperAdmin, impl.rbacEnforcementUtil.CheckAuthorizationByEmailInBatchForGlobalEnvironment) if err != nil { impl.logger.Errorw("service err, GetCombinedEnvironmentListForDropDown", "err", err) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) @@ -370,21 +352,6 @@ func (impl EnvironmentRestHandlerImpl) GetCombinedEnvironmentListForDropDown(w h common.WriteJsonResp(w, err, clusters, http.StatusOK) } -func (handler EnvironmentRestHandlerImpl) CheckAuthorizationByEmailInBatchForGlobalEnvironment(token string, object []string) map[string]bool { - var objectResult map[string]bool - if len(object) > 0 { - objectResult = handler.enforcer.EnforceInBatch(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, object) - } - return objectResult -} - -func (handler EnvironmentRestHandlerImpl) CheckAuthorizationForGlobalEnvironment(token string, object string) bool { - if ok := handler.enforcer.Enforce(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, object); !ok { - return false - } - return true -} - func (impl EnvironmentRestHandlerImpl) DeleteEnvironment(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) userId, err := impl.userService.GetLoggedInUser(r) @@ -446,7 +413,7 @@ func (impl EnvironmentRestHandlerImpl) GetCombinedEnvironmentListForDropDownByCl } } token := r.Header.Get("token") - clusters, err := impl.environmentClusterMappingsService.GetCombinedEnvironmentListForDropDownByClusterIds(token, clusterIds, impl.CheckAuthorizationForGlobalEnvironment) + clusters, err := impl.environmentClusterMappingsService.GetCombinedEnvironmentListForDropDownByClusterIds(token, clusterIds, impl.rbacEnforcementUtil.CheckAuthorizationForGlobalEnvironment) if err != nil { impl.logger.Errorw("service err, GetCombinedEnvironmentListForDropDown", "err", err) common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) diff --git a/api/restHandler/PolicyRestHandler.go b/api/restHandler/PolicyRestHandler.go index 8bea2f15d7..33c6e4d114 100644 --- a/api/restHandler/PolicyRestHandler.go +++ b/api/restHandler/PolicyRestHandler.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/cluster/environment" "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning" securityBean "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/repository/bean" @@ -103,14 +104,14 @@ func (impl PolicyRestHandlerImpl) SavePolicy(w http.ResponseWriter, r *http.Requ } } else { // for global and cluster level check super admin access only - roles, err := impl.userService.CheckUserRoles(userId) + roles, err := impl.userService.CheckUserRoles(userId, "") if err != nil { common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) return } superAdmin := false for _, item := range roles { - if item == bean.SUPERADMIN { + if item == bean2.SUPERADMIN { superAdmin = true } } @@ -173,14 +174,14 @@ func (impl PolicyRestHandlerImpl) UpdatePolicy(w http.ResponseWriter, r *http.Re } } else { // for global and cluster level check super admin access only - roles, err := impl.userService.CheckUserRoles(userId) + roles, err := impl.userService.CheckUserRoles(userId, "") if err != nil { common.WriteJsonResp(w, err, "Failed to get user by id", http.StatusInternalServerError) return } superAdmin := false for _, item := range roles { - if item == bean.SUPERADMIN { + if item == bean2.SUPERADMIN { superAdmin = true } } diff --git a/api/restHandler/app/appInfo/AppInfoRestHandler.go b/api/restHandler/app/appInfo/AppInfoRestHandler.go index 7387431330..d15c4c77b7 100644 --- a/api/restHandler/app/appInfo/AppInfoRestHandler.go +++ b/api/restHandler/app/appInfo/AppInfoRestHandler.go @@ -19,6 +19,7 @@ package appInfo import ( "encoding/json" client "github.com/devtron-labs/devtron/api/helm-app/service" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "net/http" "strconv" "strings" @@ -48,31 +49,34 @@ type AppInfoRestHandler interface { } type AppInfoRestHandlerImpl struct { - logger *zap.SugaredLogger - appService app.AppCrudOperationService - userAuthService user.UserService - validator *validator.Validate - enforcerUtil rbac.EnforcerUtil - enforcer casbin.Enforcer - helmAppService client.HelmAppService - enforcerUtilHelm rbac.EnforcerUtilHelm - genericNoteService genericNotes.GenericNoteService + logger *zap.SugaredLogger + appService app.AppCrudOperationService + userAuthService user.UserService + validator *validator.Validate + enforcerUtil rbac.EnforcerUtil + enforcer casbin.Enforcer + helmAppService client.HelmAppService + enforcerUtilHelm rbac.EnforcerUtilHelm + genericNoteService genericNotes.GenericNoteService + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil } func NewAppInfoRestHandlerImpl(logger *zap.SugaredLogger, appService app.AppCrudOperationService, userAuthService user.UserService, validator *validator.Validate, enforcerUtil rbac.EnforcerUtil, enforcer casbin.Enforcer, helmAppService client.HelmAppService, enforcerUtilHelm rbac.EnforcerUtilHelm, - genericNoteService genericNotes.GenericNoteService) *AppInfoRestHandlerImpl { + genericNoteService genericNotes.GenericNoteService, + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil) *AppInfoRestHandlerImpl { handler := &AppInfoRestHandlerImpl{ - logger: logger, - appService: appService, - userAuthService: userAuthService, - validator: validator, - enforcerUtil: enforcerUtil, - enforcer: enforcer, - helmAppService: helmAppService, - enforcerUtilHelm: enforcerUtilHelm, - genericNoteService: genericNoteService, + logger: logger, + appService: appService, + userAuthService: userAuthService, + validator: validator, + enforcerUtil: enforcerUtil, + enforcer: enforcer, + helmAppService: helmAppService, + enforcerUtilHelm: enforcerUtilHelm, + genericNoteService: genericNoteService, + rbacEnforcementUtil: rbacEnforcementUtil, } return handler } @@ -299,7 +303,6 @@ func (handler AppInfoRestHandlerImpl) GetAppListByTeamIds(w http.ResponseWriter, return } token := r.Header.Get("token") - isActionUserSuperAdmin := handler.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") appType := v.Get("appType") handler.logger.Infow("request payload, GetAppListByTeamIds", "payload", params) @@ -321,23 +324,7 @@ func (handler AppInfoRestHandlerImpl) GetAppListByTeamIds(w http.ResponseWriter, } // RBAC - for _, project := range projectWiseApps { - var accessedApps []*app.AppBean - for _, app := range project.AppList { - if isActionUserSuperAdmin { - accessedApps = append(accessedApps, app) - continue - } - object := handler.enforcerUtil.GetAppRBACNameByAppAndProjectName(project.ProjectName, app.Name) - if ok := handler.enforcer.Enforce(token, casbin.ResourceApplications, casbin.ActionGet, object); ok { - accessedApps = append(accessedApps, app) - } - } - if len(accessedApps) == 0 { - accessedApps = make([]*app.AppBean, 0) - } - project.AppList = accessedApps - } + projectWiseApps = handler.rbacEnforcementUtil.CheckAuthorisationOnApp(token, projectWiseApps) // RBAC common.WriteJsonResp(w, err, projectWiseApps, http.StatusOK) } diff --git a/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go b/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go index 1c53a8694a..4c1d9c8ceb 100644 --- a/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go +++ b/api/restHandler/app/pipeline/configure/DeploymentPipelineRestHandler.go @@ -22,6 +22,8 @@ import ( "errors" "fmt" bean3 "github.com/devtron-labs/devtron/pkg/chart/bean" + + bean4 "github.com/devtron-labs/devtron/pkg/auth/user/bean" devtronAppGitOpConfigBean "github.com/devtron-labs/devtron/pkg/chart/gitOpsConfig/bean" chartRefBean "github.com/devtron-labs/devtron/pkg/deployment/manifest/deploymentTemplate/chartRef/bean" "github.com/devtron-labs/devtron/pkg/policyGovernance/security/imageScanning/repository" @@ -616,7 +618,7 @@ func (handler *PipelineConfigRestHandlerImpl) ChangeChartRef(w http.ResponseWrit createResp, err := handler.propertiesConfigService.CreateEnvironmentProperties(request.AppId, envConfigProperties) if err != nil { - if err.Error() == bean2.NOCHARTEXIST { + if err.Error() == bean4.NOCHARTEXIST { ctx, cancel := context.WithCancel(r.Context()) if cn, ok := w.(http.CloseNotifier); ok { go func(done <-chan struct{}, closed <-chan bool) { @@ -718,7 +720,7 @@ func (handler *PipelineConfigRestHandlerImpl) EnvConfigOverrideCreate(w http.Res createResp, err := handler.propertiesConfigService.CreateEnvironmentProperties(appId, &envConfigProperties) if err != nil { - if err.Error() == bean2.NOCHARTEXIST { + if err.Error() == bean4.NOCHARTEXIST { ctx, cancel := context.WithCancel(r.Context()) if cn, ok := w.(http.CloseNotifier); ok { go func(done <-chan struct{}, closed <-chan bool) { diff --git a/api/router/router.go b/api/router/router.go index 3c49814712..0ea41a35a8 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -44,6 +44,7 @@ import ( "github.com/devtron-labs/devtron/api/server" "github.com/devtron-labs/devtron/api/team" terminal2 "github.com/devtron-labs/devtron/api/terminal" + "github.com/devtron-labs/devtron/api/userResource" webhookHelm "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/client/cron" "github.com/devtron-labs/devtron/client/dashboard" @@ -122,6 +123,7 @@ type MuxRouter struct { fluxApplicationRouter fluxApplication2.FluxApplicationRouter devtronResourceRouter devtronResource.DevtronResourceRouter scanningResultRouter resourceScan.ScanningResultRouter + userResourceRouter userResource.Router } func NewMuxRouter(logger *zap.SugaredLogger, @@ -156,6 +158,7 @@ func NewMuxRouter(logger *zap.SugaredLogger, devtronResourceRouter devtronResource.DevtronResourceRouter, fluxApplicationRouter fluxApplication2.FluxApplicationRouter, scanningResultRouter resourceScan.ScanningResultRouter, + userResourceRouter userResource.Router, ) *MuxRouter { r := &MuxRouter{ Router: mux.NewRouter(), @@ -222,6 +225,7 @@ func NewMuxRouter(logger *zap.SugaredLogger, devtronResourceRouter: devtronResourceRouter, fluxApplicationRouter: fluxApplicationRouter, scanningResultRouter: scanningResultRouter, + userResourceRouter: userResourceRouter, } return r } @@ -428,6 +432,9 @@ func (r MuxRouter) Init() { devtronResourceRouter := r.Router.PathPrefix("/orchestrator/resource").Subrouter() r.devtronResourceRouter.InitDevtronResourceRouter(devtronResourceRouter) + userResourcesRouter := r.Router.PathPrefix("/orchestrator/user/resource").Subrouter() + r.userResourceRouter.InitUserResourceRouter(userResourcesRouter) + infraConfigRouter := r.Router.PathPrefix("/orchestrator/infra-config").Subrouter() r.infraConfigRouter.InitInfraConfigRouter(infraConfigRouter) diff --git a/api/userResource/UserResourceRestHandler.go b/api/userResource/UserResourceRestHandler.go new file mode 100644 index 0000000000..b410373ad5 --- /dev/null +++ b/api/userResource/UserResourceRestHandler.go @@ -0,0 +1,73 @@ +package userResource + +import ( + "encoding/json" + "github.com/devtron-labs/devtron/api/restHandler/common" + "github.com/devtron-labs/devtron/api/userResource/adapter" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/pkg/auth/user" + "github.com/devtron-labs/devtron/pkg/userResource" + "github.com/gorilla/mux" + "go.uber.org/zap" + "net/http" +) + +type RestHandler interface { + GetResourceOptions(w http.ResponseWriter, r *http.Request) +} +type RestHandlerImpl struct { + logger *zap.SugaredLogger + userService user.UserService + userResourceService userResource.UserResourceService +} + +func NewUserResourceRestHandler(logger *zap.SugaredLogger, + userService user.UserService, + userResourceService userResource.UserResourceService) *RestHandlerImpl { + return &RestHandlerImpl{ + logger: logger, + userService: userService, + userResourceService: userResourceService, + } +} + +func (handler *RestHandlerImpl) GetResourceOptions(w http.ResponseWriter, r *http.Request) { + userId, err := handler.userService.GetLoggedInUser(r) + if userId == 0 || err != nil { + common.WriteJsonResp(w, err, "Unauthorized User", http.StatusUnauthorized) + return + } + + pathParams, caughtError := decodePathParams(w, r) + if caughtError { + return + } + decoder := json.NewDecoder(r.Body) + var reqBean apiBean.ResourceOptionsReqDto + err = decoder.Decode(&reqBean) + if err != nil { + handler.logger.Errorw("error in decoding request body", "err", err, "requestBody", r.Body) + common.WriteJsonResp(w, err, nil, http.StatusBadRequest) + return + } + token := r.Header.Get("token") + // rbac enforcement is managed at service level based on entity and kind + data, err := handler.userResourceService.GetResourceOptions(r.Context(), token, &reqBean, pathParams) + if err != nil { + handler.logger.Errorw("service error, GetResourceOptions", "err", err) + common.WriteJsonResp(w, err, nil, http.StatusInternalServerError) + return + } + + common.WriteJsonResp(w, err, data, http.StatusOK) + return + +} + +func decodePathParams(w http.ResponseWriter, r *http.Request) (pathParams *apiBean.PathParams, caughtError bool) { + vars := mux.Vars(r) + kindVar := vars[apiBean.PathParamKind] + versionVar := vars[apiBean.PathParamVersion] + pathParams = adapter.BuildPathParams(kindVar, versionVar) + return pathParams, caughtError +} diff --git a/api/userResource/UserResourceRouter.go b/api/userResource/UserResourceRouter.go new file mode 100644 index 0000000000..44dc7c7d1d --- /dev/null +++ b/api/userResource/UserResourceRouter.go @@ -0,0 +1,23 @@ +package userResource + +import "github.com/gorilla/mux" + +type Router interface { + InitUserResourceRouter(userResourceRouter *mux.Router) +} + +type RouterImpl struct { + restHandler RestHandler +} + +func NewUserResourceRouterImpl(restHandler RestHandler) *RouterImpl { + return &RouterImpl{ + restHandler: restHandler, + } +} + +func (router *RouterImpl) InitUserResourceRouter(userResourceRouter *mux.Router) { + userResourceRouter.Path("/options/{kind:[a-zA-Z0-9/-]+}/{version:[a-zA-Z0-9]+}"). + HandlerFunc(router.restHandler.GetResourceOptions).Methods("POST") + +} diff --git a/api/userResource/adapter/adapter.go b/api/userResource/adapter/adapter.go new file mode 100644 index 0000000000..fbff97d6d3 --- /dev/null +++ b/api/userResource/adapter/adapter.go @@ -0,0 +1,12 @@ +package adapter + +import ( + "github.com/devtron-labs/devtron/api/userResource/bean" +) + +func BuildPathParams(kind, version string) *bean.PathParams { + return &bean.PathParams{ + Kind: kind, + Version: version, + } +} diff --git a/api/userResource/bean/bean.go b/api/userResource/bean/bean.go new file mode 100644 index 0000000000..b6f5c9ea2f --- /dev/null +++ b/api/userResource/bean/bean.go @@ -0,0 +1,37 @@ +package bean + +import ( + bean2 "github.com/devtron-labs/devtron/pkg/appWorkflow/bean" + bean3 "github.com/devtron-labs/devtron/pkg/k8s/bean" +) + +const ( + PathParamKind = "kind" + PathParamVersion = "version" +) + +type PathParams struct { + Kind string + Version string +} + +type ResourceOptionsReqDto struct { + EntityAccessType + AppAndJobReqDto + ClusterReqDto + JobWorkflowReqDto +} +type EntityAccessType struct { + Entity string `json:"entity"` + AccessType string `json:"accessType"` +} + +type AppAndJobReqDto struct { + TeamIds []int `json:"teamIds"` +} +type ClusterReqDto struct { + *bean3.ResourceRequestBean +} +type JobWorkflowReqDto struct { + *bean2.WorkflowNamesRequest +} diff --git a/api/userResource/wire_userResource.go b/api/userResource/wire_userResource.go new file mode 100644 index 0000000000..0c53df08ad --- /dev/null +++ b/api/userResource/wire_userResource.go @@ -0,0 +1,28 @@ +package userResource + +import ( + "github.com/devtron-labs/devtron/pkg/userResource" + "github.com/google/wire" +) + +var UserResourceWireSet = wire.NewSet( + NewUserResourceRouterImpl, + wire.Bind(new(Router), new(*RouterImpl)), + + NewUserResourceRestHandler, + wire.Bind(new(RestHandler), new(*RestHandlerImpl)), + + userResource.NewUserResourceExtendedServiceImpl, + wire.Bind(new(userResource.UserResourceService), new(*userResource.UserResourceExtendedServiceImpl)), +) + +var UserResourceWireSetEA = wire.NewSet( + NewUserResourceRouterImpl, + wire.Bind(new(Router), new(*RouterImpl)), + + NewUserResourceRestHandler, + wire.Bind(new(RestHandler), new(*RestHandlerImpl)), + + userResource.NewUserResourceServiceImpl, + wire.Bind(new(userResource.UserResourceService), new(*userResource.UserResourceServiceImpl)), +) diff --git a/client/telemetry/TelemetryEventClient.go b/client/telemetry/TelemetryEventClient.go index 3be2cfa185..4d60e778c9 100644 --- a/client/telemetry/TelemetryEventClient.go +++ b/client/telemetry/TelemetryEventClient.go @@ -26,6 +26,7 @@ import ( "github.com/devtron-labs/devtron/api/helm-app/gRPC" installedAppReader "github.com/devtron-labs/devtron/pkg/appStore/installedApp/read" bean2 "github.com/devtron-labs/devtron/pkg/attributes/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" bean3 "github.com/devtron-labs/devtron/pkg/cluster/bean" module2 "github.com/devtron-labs/devtron/pkg/module/bean" cron3 "github.com/devtron-labs/devtron/util/cron" @@ -33,7 +34,6 @@ import ( "time" "github.com/devtron-labs/common-lib/utils/k8s" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/internal/sql/repository" "github.com/devtron-labs/devtron/pkg/auth/sso" user2 "github.com/devtron-labs/devtron/pkg/auth/user" diff --git a/cmd/external-app/router.go b/cmd/external-app/router.go index d7047fcaf4..f19c440ea0 100644 --- a/cmd/external-app/router.go +++ b/cmd/external-app/router.go @@ -41,6 +41,7 @@ import ( "github.com/devtron-labs/devtron/api/server" "github.com/devtron-labs/devtron/api/team" "github.com/devtron-labs/devtron/api/terminal" + "github.com/devtron-labs/devtron/api/userResource" webhookHelm "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/client/dashboard" "github.com/devtron-labs/devtron/util" @@ -85,6 +86,7 @@ type MuxRouter struct { rbacRoleRouter user.RbacRoleRouter argoApplicationRouter argoApplication.ArgoApplicationRouter fluxApplicationRouter fluxApplication.FluxApplicationRouter + userResourceRouter userResource.Router } func NewMuxRouter( @@ -118,6 +120,7 @@ func NewMuxRouter( attributesRouter router.AttributesRouter, appRouter app.AppRouterEAMode, rbacRoleRouter user.RbacRoleRouter, argoApplicationRouter argoApplication.ArgoApplicationRouter, fluxApplicationRouter fluxApplication.FluxApplicationRouter, + userResourceRouter userResource.Router, ) *MuxRouter { r := &MuxRouter{ Router: mux.NewRouter(), @@ -154,6 +157,7 @@ func NewMuxRouter( rbacRoleRouter: rbacRoleRouter, argoApplicationRouter: argoApplicationRouter, fluxApplicationRouter: fluxApplicationRouter, + userResourceRouter: userResourceRouter, } return r } @@ -294,4 +298,7 @@ func (r *MuxRouter) Init() { commonRouter := r.Router.PathPrefix("/orchestrator/global").Subrouter() r.commonRouter.InitCommonRouter(commonRouter) + + userResourcesRouter := r.Router.PathPrefix("/orchestrator/user/resource").Subrouter() + r.userResourceRouter.InitUserResourceRouter(userResourcesRouter) } diff --git a/cmd/external-app/wire.go b/cmd/external-app/wire.go index 3130682556..15b1aeae72 100644 --- a/cmd/external-app/wire.go +++ b/cmd/external-app/wire.go @@ -51,6 +51,7 @@ import ( "github.com/devtron-labs/devtron/api/server" "github.com/devtron-labs/devtron/api/team" "github.com/devtron-labs/devtron/api/terminal" + "github.com/devtron-labs/devtron/api/userResource" webhookHelm "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/client/argocdServer" "github.com/devtron-labs/devtron/client/argocdServer/bean" @@ -87,6 +88,7 @@ import ( "github.com/devtron-labs/devtron/pkg/sql" util2 "github.com/devtron-labs/devtron/pkg/util" util3 "github.com/devtron-labs/devtron/util" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/devtron-labs/devtron/util/cron" "github.com/devtron-labs/devtron/util/rbac" "github.com/google/wire" @@ -123,6 +125,7 @@ func InitializeApp() (*App, error) { providerConfig.DeploymentProviderConfigWireSet, argoApplication.ArgoApplicationWireSetEA, fluxApplication.FluxApplicationWireSet, + userResource.UserResourceWireSetEA, NewApp, NewMuxRouter, util.NewHttpClient, @@ -143,6 +146,9 @@ func InitializeApp() (*App, error) { rbac.NewEnforcerUtilImpl, wire.Bind(new(rbac.EnforcerUtil), new(*rbac.EnforcerUtilImpl)), + commonEnforcementFunctionsUtil.NewCommonEnforcementUtilImpl, + wire.Bind(new(commonEnforcementFunctionsUtil.CommonEnforcementUtil), new(*commonEnforcementFunctionsUtil.CommonEnforcementUtilImpl)), + appInfo2.NewAppInfoRouterImpl, wire.Bind(new(appInfo2.AppInfoRouter), new(*appInfo2.AppInfoRouterImpl)), appInfo.NewAppInfoRestHandlerImpl, diff --git a/cmd/external-app/wire_gen.go b/cmd/external-app/wire_gen.go index 0b8bc14913..f172415218 100644 --- a/cmd/external-app/wire_gen.go +++ b/cmd/external-app/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -44,6 +44,7 @@ import ( server2 "github.com/devtron-labs/devtron/api/server" team2 "github.com/devtron-labs/devtron/api/team" terminal2 "github.com/devtron-labs/devtron/api/terminal" + userResource2 "github.com/devtron-labs/devtron/api/userResource" webhookHelm2 "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/client/argocdServer" "github.com/devtron-labs/devtron/client/argocdServer/bean" @@ -128,9 +129,11 @@ import ( "github.com/devtron-labs/devtron/pkg/team/read" repository2 "github.com/devtron-labs/devtron/pkg/team/repository" "github.com/devtron-labs/devtron/pkg/terminal" + "github.com/devtron-labs/devtron/pkg/userResource" util3 "github.com/devtron-labs/devtron/pkg/util" "github.com/devtron-labs/devtron/pkg/webhook/helm" util2 "github.com/devtron-labs/devtron/util" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/devtron-labs/devtron/util/cron" "github.com/devtron-labs/devtron/util/rbac" ) @@ -198,7 +201,8 @@ func InitializeApp() (*App, error) { } userAuditRepositoryImpl := repository.NewUserAuditRepositoryImpl(db) userAuditServiceImpl := user.NewUserAuditServiceImpl(sugaredLogger, userAuditRepositoryImpl) - userServiceImpl := user.NewUserServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, sessionManager, userCommonServiceImpl, userAuditServiceImpl) + roleGroupServiceImpl := user.NewRoleGroupServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, userCommonServiceImpl) + userServiceImpl := user.NewUserServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, sessionManager, userCommonServiceImpl, userAuditServiceImpl, roleGroupServiceImpl) ssoLoginRepositoryImpl := sso.NewSSOLoginRepositoryImpl(db, sugaredLogger) k8sRuntimeConfig, err := k8s.GetRuntimeConfig() if err != nil { @@ -290,23 +294,23 @@ func InitializeApp() (*App, error) { teamRouterImpl := team2.NewTeamRouterImpl(teamRestHandlerImpl) 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) + transactionUtilImpl := sql.NewTransactionUtilImpl(db) + ciPipelineRepositoryImpl := pipelineConfig.NewCiPipelineRepositoryImpl(db, sugaredLogger, transactionUtilImpl) + enforcerUtilImpl := rbac.NewEnforcerUtilImpl(sugaredLogger, teamRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, clusterRepositoryImpl, enforcerImpl, dbMigrationServiceImpl, teamReadServiceImpl) + commonEnforcementUtilImpl := commonEnforcementFunctionsUtil.NewCommonEnforcementUtilImpl(enforcerImpl, enforcerUtilImpl, sugaredLogger, userServiceImpl, userCommonServiceImpl) + userRestHandlerImpl := user2.NewUserRestHandlerImpl(userServiceImpl, validate, sugaredLogger, enforcerImpl, roleGroupServiceImpl, userCommonServiceImpl, commonEnforcementUtilImpl) userRouterImpl := user2.NewUserRouterImpl(userRestHandlerImpl) moduleRepositoryImpl := moduleRepo.NewModuleRepositoryImpl(db) moduleReadServiceImpl := read5.NewModuleReadServiceImpl(sugaredLogger, moduleRepositoryImpl) commonBaseServiceImpl := commonService.NewCommonBaseServiceImpl(sugaredLogger, environmentVariables, moduleReadServiceImpl) commonRestHandlerImpl := restHandler.NewCommonRestHandlerImpl(sugaredLogger, userServiceImpl, commonBaseServiceImpl) commonRouterImpl := router.NewCommonRouterImpl(commonRestHandlerImpl) - transactionUtilImpl := sql.NewTransactionUtilImpl(db) genericNoteRepositoryImpl := repository8.NewGenericNoteRepositoryImpl(db, transactionUtilImpl) genericNoteHistoryRepositoryImpl := repository8.NewGenericNoteHistoryRepositoryImpl(db, transactionUtilImpl) genericNoteHistoryServiceImpl := genericNotes.NewGenericNoteHistoryServiceImpl(genericNoteHistoryRepositoryImpl, sugaredLogger) genericNoteServiceImpl := genericNotes.NewGenericNoteServiceImpl(genericNoteRepositoryImpl, genericNoteHistoryServiceImpl, userRepositoryImpl, sugaredLogger) clusterDescriptionRepositoryImpl := repository3.NewClusterDescriptionRepositoryImpl(db, sugaredLogger) clusterDescriptionServiceImpl := cluster.NewClusterDescriptionServiceImpl(clusterDescriptionRepositoryImpl, userRepositoryImpl, sugaredLogger) - ciPipelineRepositoryImpl := pipelineConfig.NewCiPipelineRepositoryImpl(db, sugaredLogger, transactionUtilImpl) - enforcerUtilImpl := rbac.NewEnforcerUtilImpl(sugaredLogger, teamRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, clusterRepositoryImpl, enforcerImpl, dbMigrationServiceImpl, teamReadServiceImpl) clusterRbacServiceImpl := rbac2.NewClusterRbacServiceImpl(environmentServiceImpl, enforcerImpl, enforcerUtilImpl, clusterServiceImpl, sugaredLogger, userServiceImpl, clusterReadServiceImpl) clusterRestHandlerImpl := cluster2.NewClusterRestHandlerImpl(clusterServiceImpl, genericNoteServiceImpl, clusterDescriptionServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceImpl, environmentServiceImpl, clusterRbacServiceImpl) clusterRouterImpl := cluster2.NewClusterRouterImpl(clusterRestHandlerImpl) @@ -359,7 +363,7 @@ func InitializeApp() (*App, error) { helmAppRestHandlerImpl := client2.NewHelmAppRestHandlerImpl(sugaredLogger, helmAppServiceImpl, enforcerImpl, clusterServiceImpl, enforcerUtilHelmImpl, appStoreDeploymentServiceImpl, installedAppDBServiceImpl, userServiceImpl, attributesServiceImpl, serverEnvConfigServerEnvConfig, fluxApplicationServiceImpl, argoApplicationServiceImpl) helmAppRouterImpl := client2.NewHelmAppRouterImpl(helmAppRestHandlerImpl) environmentReadServiceImpl := read8.NewEnvironmentReadServiceImpl(sugaredLogger, environmentRepositoryImpl) - environmentRestHandlerImpl := cluster2.NewEnvironmentRestHandlerImpl(environmentServiceImpl, environmentReadServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceImpl, k8sServiceImpl, k8sCommonServiceImpl) + environmentRestHandlerImpl := cluster2.NewEnvironmentRestHandlerImpl(environmentServiceImpl, environmentReadServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceImpl, k8sServiceImpl, k8sCommonServiceImpl, commonEnforcementUtilImpl) environmentRouterImpl := cluster2.NewEnvironmentRouterImpl(environmentRestHandlerImpl) argoApplicationReadServiceImpl := read9.NewArgoApplicationReadServiceImpl(sugaredLogger, clusterRepositoryImpl, k8sServiceImpl, helmAppClientImpl, helmAppServiceImpl) k8sApplicationRestHandlerImpl := application2.NewK8sApplicationRestHandlerImpl(sugaredLogger, k8sApplicationServiceImpl, pumpImpl, terminalSessionHandlerImpl, enforcerImpl, enforcerUtilHelmImpl, enforcerUtilImpl, helmAppServiceImpl, userServiceImpl, k8sCommonServiceImpl, validate, environmentVariables, fluxApplicationServiceImpl, argoApplicationReadServiceImpl) @@ -469,7 +473,7 @@ func InitializeApp() (*App, error) { materialRepositoryImpl := repository12.NewMaterialRepositoryImpl(db) gitMaterialReadServiceImpl := read10.NewGitMaterialReadServiceImpl(sugaredLogger, materialRepositoryImpl) appCrudOperationServiceImpl := app2.NewAppCrudOperationServiceImpl(appLabelRepositoryImpl, sugaredLogger, appRepositoryImpl, userRepositoryImpl, installedAppRepositoryImpl, genericNoteServiceImpl, installedAppDBServiceImpl, crudOperationServiceConfig, dbMigrationServiceImpl, gitMaterialReadServiceImpl) - appInfoRestHandlerImpl := appInfo.NewAppInfoRestHandlerImpl(sugaredLogger, appCrudOperationServiceImpl, userServiceImpl, validate, enforcerUtilImpl, enforcerImpl, helmAppServiceImpl, enforcerUtilHelmImpl, genericNoteServiceImpl) + appInfoRestHandlerImpl := appInfo.NewAppInfoRestHandlerImpl(sugaredLogger, appCrudOperationServiceImpl, userServiceImpl, validate, enforcerUtilImpl, enforcerImpl, helmAppServiceImpl, enforcerUtilHelmImpl, genericNoteServiceImpl, commonEnforcementUtilImpl) appInfoRouterImpl := appInfo2.NewAppInfoRouterImpl(sugaredLogger, appInfoRestHandlerImpl) appFilteringRestHandlerImpl := appList.NewAppFilteringRestHandlerImpl(sugaredLogger, teamServiceImpl, enforcerImpl, userServiceImpl, clusterServiceImpl, environmentServiceImpl, teamReadServiceImpl) appFilteringRouterImpl := appList2.NewAppFilteringRouterImpl(appFilteringRestHandlerImpl) @@ -481,7 +485,10 @@ func InitializeApp() (*App, error) { argoApplicationRouterImpl := argoApplication2.NewArgoApplicationRouterImpl(argoApplicationRestHandlerImpl) fluxApplicationRestHandlerImpl := fluxApplication2.NewFluxApplicationRestHandlerImpl(fluxApplicationServiceImpl, sugaredLogger, enforcerImpl) fluxApplicationRouterImpl := fluxApplication2.NewFluxApplicationRouterImpl(fluxApplicationRestHandlerImpl) - muxRouter := NewMuxRouter(sugaredLogger, ssoLoginRouterImpl, teamRouterImpl, userAuthRouterImpl, userRouterImpl, commonRouterImpl, clusterRouterImpl, dashboardRouterImpl, helmAppRouterImpl, environmentRouterImpl, k8sApplicationRouterImpl, chartRepositoryRouterImpl, appStoreDiscoverRouterImpl, appStoreValuesRouterImpl, appStoreDeploymentRouterImpl, chartProviderRouterImpl, dockerRegRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, userAttributesRouterImpl, telemetryRouterImpl, userTerminalAccessRouterImpl, attributesRouterImpl, appRouterEAModeImpl, rbacRoleRouterImpl, argoApplicationRouterImpl, fluxApplicationRouterImpl) + userResourceServiceImpl := userResource.NewUserResourceServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, clusterServiceImpl, k8sApplicationServiceImpl, enforcerUtilImpl, commonEnforcementUtilImpl, enforcerImpl, appCrudOperationServiceImpl) + restHandlerImpl := userResource2.NewUserResourceRestHandler(sugaredLogger, userServiceImpl, userResourceServiceImpl) + routerImpl := userResource2.NewUserResourceRouterImpl(restHandlerImpl) + muxRouter := NewMuxRouter(sugaredLogger, ssoLoginRouterImpl, teamRouterImpl, userAuthRouterImpl, userRouterImpl, commonRouterImpl, clusterRouterImpl, dashboardRouterImpl, helmAppRouterImpl, environmentRouterImpl, k8sApplicationRouterImpl, chartRepositoryRouterImpl, appStoreDiscoverRouterImpl, appStoreValuesRouterImpl, appStoreDeploymentRouterImpl, chartProviderRouterImpl, dockerRegRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, userAttributesRouterImpl, telemetryRouterImpl, userTerminalAccessRouterImpl, attributesRouterImpl, appRouterEAModeImpl, rbacRoleRouterImpl, argoApplicationRouterImpl, fluxApplicationRouterImpl, routerImpl) mainApp := NewApp(db, sessionManager, muxRouter, telemetryEventClientImpl, posthogClient, sugaredLogger) return mainApp, nil } diff --git a/pkg/apiToken/ApiTokenService.go b/pkg/apiToken/ApiTokenService.go index 5ddcb6eb55..f2d2a8fd06 100644 --- a/pkg/apiToken/ApiTokenService.go +++ b/pkg/apiToken/ApiTokenService.go @@ -26,7 +26,6 @@ import ( "time" "github.com/devtron-labs/authenticator/middleware" - "github.com/devtron-labs/devtron/api/bean" openapi "github.com/devtron-labs/devtron/api/openapi/openapiClient" user2 "github.com/devtron-labs/devtron/pkg/auth/user" "github.com/devtron-labs/devtron/pkg/sql" @@ -200,10 +199,10 @@ func (impl ApiTokenServiceImpl) CreateApiToken(request *openapi.CreateApiTokenRe } // step-4 - Create user using email - createUserRequest := bean.UserInfo{ + createUserRequest := userBean.UserInfo{ UserId: createdBy, EmailId: email, - UserType: bean.USER_TYPE_API_TOKEN, + UserType: userBean.USER_TYPE_API_TOKEN, } createUserResponse, err := impl.userService.CreateUser(&createUserRequest, token, managerAuth) if err != nil { @@ -334,7 +333,7 @@ func (impl ApiTokenServiceImpl) DeleteApiToken(apiTokenId int, deletedBy int32) } // step-2 inactivate user corresponds to this api-token - deleteUserRequest := bean.UserInfo{ + deleteUserRequest := userBean.UserInfo{ Id: apiToken.UserId, UserId: deletedBy, } diff --git a/pkg/appClone/AppCloneService.go b/pkg/appClone/AppCloneService.go index 4c7fd14bfc..61813b2bde 100644 --- a/pkg/appClone/AppCloneService.go +++ b/pkg/appClone/AppCloneService.go @@ -19,7 +19,6 @@ package appClone import ( "context" "fmt" - bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/api/bean/AppView" "github.com/devtron-labs/devtron/internal/constants" app2 "github.com/devtron-labs/devtron/internal/sql/repository/app" @@ -31,6 +30,7 @@ import ( "github.com/devtron-labs/devtron/pkg/appWorkflow" bean4 "github.com/devtron-labs/devtron/pkg/appWorkflow/bean" "github.com/devtron-labs/devtron/pkg/attributes" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/bean" pipeline2 "github.com/devtron-labs/devtron/pkg/build/pipeline" "github.com/devtron-labs/devtron/pkg/chart" diff --git a/pkg/appStore/chartGroup/ChartGroupService.go b/pkg/appStore/chartGroup/ChartGroupService.go index 2f965cb8dd..aeb248e9e2 100644 --- a/pkg/appStore/chartGroup/ChartGroupService.go +++ b/pkg/appStore/chartGroup/ChartGroupService.go @@ -137,7 +137,7 @@ type ChartGroupService interface { UpdateChartGroup(req *ChartGroupBean) (*ChartGroupBean, error) SaveChartGroupEntries(req *ChartGroupBean) (*ChartGroupBean, error) GetChartGroupWithChartMetaData(chartGroupId int) (*ChartGroupBean, error) - ChartGroupList(max int) (*ChartGroupList, error) + GetChartGroupList(max int) (*ChartGroupList, error) GetChartGroupWithInstallationDetail(chartGroupId int) (*ChartGroupBean, error) ChartGroupListMin(max int) ([]*ChartGroupBean, error) DeleteChartGroup(req *ChartGroupBean) error @@ -375,7 +375,7 @@ func (impl *ChartGroupServiceImpl) charterEntryAdopter(chartGroupEntry *reposito return entry } -func (impl *ChartGroupServiceImpl) ChartGroupList(max int) (*ChartGroupList, error) { +func (impl *ChartGroupServiceImpl) GetChartGroupList(max int) (*ChartGroupList, error) { groups, err := impl.chartGroupRepository.GetAll(max) if err != nil { return nil, err diff --git a/pkg/auth/authorisation/casbin/Adapter.go b/pkg/auth/authorisation/casbin/Adapter.go index 85e73ddb19..5f70f5180d 100644 --- a/pkg/auth/authorisation/casbin/Adapter.go +++ b/pkg/auth/authorisation/casbin/Adapter.go @@ -18,6 +18,7 @@ package casbin import ( "fmt" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" "log" "os" "strings" @@ -45,20 +46,6 @@ var e2 *casbinv2.SyncedEnforcer var enforcerImplRef *EnforcerImpl var casbinVersion Version -type Subject string -type Resource string -type Action string -type Object string -type PolicyType string - -type Policy struct { - Type PolicyType `json:"type"` - Sub Subject `json:"sub"` - Res Resource `json:"res"` - Act Action `json:"act"` - Obj Object `json:"obj"` -} - func isV2() bool { return casbinVersion == CasbinV2 } @@ -151,9 +138,9 @@ func setEnforcerImpl(ref *EnforcerImpl) { enforcerImplRef = ref } -func AddPolicy(policies []Policy) []Policy { +func AddPolicy(policies []bean.Policy) []bean.Policy { defer handlePanic() - var failed = []Policy{} + var failed = []bean.Policy{} emailIdList := map[string]struct{}{} var err error for _, p := range policies { @@ -207,9 +194,9 @@ func LoadPolicy() { } } -func RemovePolicy(policies []Policy) []Policy { +func RemovePolicy(policies []bean.Policy) []bean.Policy { defer handlePanic() - var failed = []Policy{} + var failed = []bean.Policy{} emailIdList := map[string]struct{}{} var err error for _, p := range policies { diff --git a/pkg/auth/authorisation/casbin/bean/bean.go b/pkg/auth/authorisation/casbin/bean/bean.go new file mode 100644 index 0000000000..b3939e39f8 --- /dev/null +++ b/pkg/auth/authorisation/casbin/bean/bean.go @@ -0,0 +1,15 @@ +package bean + +type Policy struct { + Type PolicyType `json:"type"` + Sub Subject `json:"sub"` + Res Resource `json:"res"` + Act Action `json:"act"` + Obj Object `json:"obj"` +} + +type Subject string +type Resource string +type Action string +type Object string +type PolicyType string diff --git a/pkg/auth/sso/SSOLoginService.go b/pkg/auth/sso/SSOLoginService.go index 5b1e8aaddd..9df62522b1 100644 --- a/pkg/auth/sso/SSOLoginService.go +++ b/pkg/auth/sso/SSOLoginService.go @@ -19,12 +19,12 @@ package sso import ( "encoding/json" "fmt" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "time" "github.com/devtron-labs/common-lib/utils/k8s" "github.com/devtron-labs/devtron/pkg/auth/authentication" - "github.com/devtron-labs/devtron/api/bean" util2 "github.com/devtron-labs/devtron/util" "github.com/go-pg/pg" "go.uber.org/zap" diff --git a/pkg/auth/user/RoleGroupService.go b/pkg/auth/user/RoleGroupService.go index d0d4c71f73..12e08e70c5 100644 --- a/pkg/auth/user/RoleGroupService.go +++ b/pkg/auth/user/RoleGroupService.go @@ -19,13 +19,15 @@ package user import ( "errors" "fmt" + bean3 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/adapter" helper2 "github.com/devtron-labs/devtron/pkg/auth/user/helper" "github.com/devtron-labs/devtron/pkg/auth/user/repository/helper" + "github.com/devtron-labs/devtron/pkg/sql" "net/http" "strings" "time" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/internal/constants" "github.com/devtron-labs/devtron/internal/util" casbin2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -38,18 +40,19 @@ 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, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean.RoleGroup, error) - FetchDetailedRoleGroups(req *bean.ListingRequest) ([]*bean.RoleGroup, error) - FetchRoleGroupsById(id int32) (*bean.RoleGroup, error) - FetchRoleGroups() ([]*bean.RoleGroup, error) - FetchRoleGroupsV2(req *bean.ListingRequest) (*bean.RoleGroupListingResponse, error) - FetchRoleGroupsWithFilters(request *bean.ListingRequest) (*bean.RoleGroupListingResponse, error) - FetchRoleGroupsByName(name string) ([]*bean.RoleGroup, error) - DeleteRoleGroup(model *bean.RoleGroup) (bool, error) - BulkDeleteRoleGroups(request *bean.BulkDeleteRequest) (bool, error) - FetchRolesForUserRoleGroups(userRoleGroups []bean.UserRoleGroup) ([]*bean.RoleFilter, error) + CreateRoleGroup(request *bean2.RoleGroup) (*bean2.RoleGroup, error) + UpdateRoleGroup(request *bean2.RoleGroup, token string, checkRBACForGroupUpdate func(token string, groupInfo *bean2.RoleGroup, + eliminatedRoleFilters []*repository.RoleModel, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean2.RoleGroup, error) + FetchDetailedRoleGroups(req *bean2.ListingRequest) ([]*bean2.RoleGroup, error) + FetchRoleGroupsById(id int32) (*bean2.RoleGroup, error) + FetchRoleGroups() ([]*bean2.RoleGroup, error) + FetchRoleGroupsV2(req *bean2.ListingRequest) (*bean2.RoleGroupListingResponse, error) + FetchRoleGroupsWithFilters(request *bean2.ListingRequest) (*bean2.RoleGroupListingResponse, error) + FetchRoleGroupsByName(name string) ([]*bean2.RoleGroup, error) + DeleteRoleGroup(model *bean2.RoleGroup) (bool, error) + BulkDeleteRoleGroups(request *bean2.BulkDeleteRequest) (bool, error) + FetchRolesForUserRoleGroups(userRoleGroups []bean2.UserRoleGroup) ([]*bean2.RoleFilter, error) + GetGroupIdVsRoleGroupMapForIds(ids []int32) (map[int32]*repository.RoleGroup, error) } type RoleGroupServiceImpl struct { @@ -74,7 +77,7 @@ func NewRoleGroupServiceImpl(userAuthRepository repository.UserAuthRepository, return serviceImpl } -func (impl RoleGroupServiceImpl) CreateRoleGroup(request *bean.RoleGroup) (*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) CreateRoleGroup(request *bean2.RoleGroup) (*bean2.RoleGroup, error) { validationPassed := util2.CheckValidationForRoleGroupCreation(request.Name) if !validationPassed { return nil, errors.New(bean2.VALIDATION_FAILED_ERROR_MSG) @@ -135,58 +138,22 @@ func (impl RoleGroupServiceImpl) CreateRoleGroup(request *bean.RoleGroup) (*bean model.Id = model.Id //Starts Role and Mapping - capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(request.RoleFilters) - var policies = make([]casbin2.Policy, 0, capacity) - if request.SuperAdmin == false { - for index, roleFilter := range request.RoleFilters { - entity := roleFilter.Entity - if entity == bean2.CLUSTER_ENTITIY { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForClusterEntity(roleFilter, request.UserId, model, nil, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - // making it non-blocking as it is being done for multiple Role filters and does not want this to be blocking. - impl.logger.Errorw("error in creating updating role group for cluster entity", "err", err, "roleFilter", roleFilter) - } - } else if entity == bean2.EntityJobs { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForJobsEntity(roleFilter, request.UserId, model, nil, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - // making it non-blocking as it is being done for multiple Role filters and does not want this to be blocking. - impl.logger.Errorw("error in creating updating role group for jobs entity", "err", err, "roleFilter", roleFilter) - } - } else { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForOtherEntity(roleFilter, request, model, nil, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - // making it non-blocking as it is being done for multiple Role filters and does not want this to be blocking. - impl.logger.Errorw("error in creating updating role group for apps entity", "err", err, "roleFilter", roleFilter) - } - } - } - } else if request.SuperAdmin == true { - flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, request.UserId) - if err != nil || flag == false { - impl.logger.Errorw("error in CreateRoleForSuperAdminIfNotExists ", "err", err, "roleGroupName", request.Name) + var policies = make([]bean3.Policy, 0) + if request.SuperAdmin { + policiesToBeAdded, err := impl.CreateAndAddPolicesForSuperAdmin(tx, request.UserId, model.Id, model.CasbinName) + if err != nil { + impl.logger.Errorw("error encountered in CreateRoleGroup", "err", err) return nil, err } - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes("", "", "", "", bean2.SUPER_ADMIN, "", "", "", "", "", "", "", false, "") + policies = append(policies, policiesToBeAdded...) + } else { + policiesToBeAdded, err := impl.createAndAddPolciesForNonSuperAdmin(tx, request.RoleFilters, request.UserId, model) if err != nil { - impl.logger.Errorw("error in getting role by filter for all Types for superAdmin", "err", err) + impl.logger.Errorw("error encountered in CreateRoleGroup", "err", err) return nil, err } - if roleModel.Id > 0 { - roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} - roleGroupMappingModel.CreatedBy = request.UserId - roleGroupMappingModel.UpdatedBy = request.UserId - roleGroupMappingModel.CreatedOn = time.Now() - roleGroupMappingModel.UpdatedOn = time.Now() - roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) - if err != nil { - impl.logger.Errorw("error in creating role group role mapping", "err", err, "RoleGroupId", model.Id) - return nil, err - } - policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) - } + policies = append(policies, policiesToBeAdded...) + } if len(policies) > 0 { @@ -204,7 +171,72 @@ func (impl RoleGroupServiceImpl) CreateRoleGroup(request *bean.RoleGroup) (*bean return request, nil } -func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForClusterEntity(roleFilter bean.RoleFilter, userId int32, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]casbin2.Policy, error) { +func (impl RoleGroupServiceImpl) CreateAndAddPolicesForSuperAdmin(tx *pg.Tx, userLoggedInId int32, roleGroupId int32, groupCasbinName string) ([]bean3.Policy, error) { + policies := make([]bean3.Policy, 0) + flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, userLoggedInId) + if err != nil || flag == false { + impl.logger.Errorw("error in CreateRoleForSuperAdminIfNotExists ", "err", err, "groupCasbinName", groupCasbinName) + return nil, err + } + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildSuperAdminRoleFieldsDto()) + if err != nil { + impl.logger.Errorw("error in getting role by filter for all Types for superAdmin", "err", err) + return nil, err + } + if roleModel.Id > 0 { + roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: roleGroupId, RoleId: roleModel.Id} + roleGroupMappingModel.AuditLog = sql.NewDefaultAuditLog(userLoggedInId) + roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + if err != nil { + impl.logger.Errorw("error in creating role group role mapping", "err", err, "RoleGroupId", roleGroupId) + return nil, err + } + policies = append(policies, adapter.GetCasbinGroupPolicy(groupCasbinName, roleModel.Role, nil)) + } + return policies, nil +} + +func (impl RoleGroupServiceImpl) createAndAddPolciesForNonSuperAdmin(tx *pg.Tx, roleFilters []bean2.RoleFilter, userLoggedInId int32, model *repository.RoleGroup) ([]bean3.Policy, error) { + capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(roleFilters) + var policies = make([]bean3.Policy, 0, capacity) + for index, roleFilter := range roleFilters { + entity := roleFilter.Entity + policiesToBeAdded, err := impl.createOrUpdateRoleGroupRoleMappingForAllTypes(tx, roleFilter, model, nil, entity, mapping[index], userLoggedInId) + if err != nil { + impl.logger.Errorw("error in CreateAndAddPoliciesForNonSuperAdmin", "err", err, "rolefilter", roleFilters) + return nil, err + } + policies = append(policies, policiesToBeAdded...) + } + return policies, nil +} + +func (impl RoleGroupServiceImpl) createOrUpdateRoleGroupRoleMappingForAllTypes(tx *pg.Tx, roleFilter bean2.RoleFilter, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, entity string, capacity int, userId int32) ([]bean3.Policy, error) { + var policiesToBeAdded = make([]bean3.Policy, 0, capacity) + var err error + if entity == bean2.CLUSTER_ENTITIY { + policiesToBeAdded, err = impl.CreateOrUpdateRoleGroupForClusterEntity(roleFilter, userId, model, existingRoles, tx, capacity) + if err != nil { + impl.logger.Errorw("error in creating updating role group for cluster entity", "err", err, "roleFilter", roleFilter) + return nil, err + } + } else if entity == bean2.EntityJobs { + policiesToBeAdded, err = impl.CreateOrUpdateRoleGroupForJobsEntity(roleFilter, userId, model, existingRoles, tx, capacity) + if err != nil { + impl.logger.Errorw("error in creating updating role group for jobs entity", "err", err, "roleFilter", roleFilter) + return nil, err + } + } else { + policiesToBeAdded, err = impl.CreateOrUpdateRoleGroupForOtherEntity(roleFilter, userId, model, existingRoles, tx, capacity) + if err != nil { + impl.logger.Errorw("error in creating updating role group for apps entity", "err", err, "roleFilter", roleFilter) + return nil, err + } + } + return policiesToBeAdded, nil +} + +func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForClusterEntity(roleFilter bean2.RoleFilter, userId int32, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]bean3.Policy, error) { //var policiesToBeAdded []casbin2.Policy namespaces := strings.Split(roleFilter.Namespace, ",") groups := strings.Split(roleFilter.Group, ",") @@ -213,46 +245,51 @@ func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForClusterEntity(roleFil actionType := roleFilter.Action accessType := roleFilter.AccessType entity := roleFilter.Entity - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + var policiesToBeAdded = make([]bean3.Policy, 0, capacity) for _, namespace := range namespaces { for _, group := range groups { for _, kind := range kinds { for _, resource := range resources { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") - if err != nil { - impl.logger.Errorw("error in getting new role model by filter") - return policiesToBeAdded, err - } - if roleModel.Id == 0 { - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes("", "", "", entity, roleFilter.Cluster, namespace, group, kind, resource, actionType, accessType, "", userId) - if err != nil || flag == false { - return policiesToBeAdded, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") + for _, subaction := range subActions { + clusterRoleFieldDto := adapter.BuildClusterRoleFieldsDto(entity, accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, subaction) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(clusterRoleFieldDto) if err != nil { + impl.logger.Errorw("error in getting new role model by filter", "roleFilter", roleFilter, "err", err) return policiesToBeAdded, err } if roleModel.Id == 0 { - continue - } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which are removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) - } else { - if roleModel.Id > 0 { - //new role ids in new array, add it - roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} - roleGroupMappingModel.CreatedBy = userId - roleGroupMappingModel.UpdatedBy = userId - roleGroupMappingModel.CreatedOn = time.Now() - roleGroupMappingModel.UpdatedOn = time.Now() - roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(clusterRoleFieldDto, userId) + if err != nil || flag == false { + return policiesToBeAdded, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(clusterRoleFieldDto) if err != nil { - return nil, err + return policiesToBeAdded, err + } + if roleModel.Id == 0 { + continue + } + } + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which are removed + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) + } else { + if roleModel.Id > 0 { + //new role ids in new array, add it + roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} + roleGroupMappingModel.CreatedBy = userId + roleGroupMappingModel.UpdatedBy = userId + roleGroupMappingModel.CreatedOn = time.Now() + roleGroupMappingModel.UpdatedOn = time.Now() + roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + if err != nil { + return nil, err + } + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) } } } @@ -262,55 +299,55 @@ func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForClusterEntity(roleFil return policiesToBeAdded, nil } -func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForOtherEntity(roleFilter bean.RoleFilter, request *bean.RoleGroup, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]casbin2.Policy, error) { +func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForOtherEntity(roleFilter bean2.RoleFilter, userLoggedInId int32, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]bean3.Policy, error) { actionType := roleFilter.Action accessType := roleFilter.AccessType entity := roleFilter.Entity entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + var policiesToBeAdded = make([]bean3.Policy, 0, capacity) for _, environment := range environments { for _, entityName := range entityNames { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, "") - if err != nil { - impl.logger.Errorw("error in getting new role model") - return nil, err - } - if roleModel.Id == 0 { - request.Status = fmt.Sprintf("%s+%s,%s,%s,%s", bean2.RoleNotFoundStatusPrefix, roleFilter.Team, environment, entityName, actionType) - if roleFilter.Entity == bean2.ENTITY_APPS || roleFilter.Entity == bean2.CHART_GROUP_ENTITY { - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(roleFilter.Team, entityName, environment, entity, "", "", "", "", "", actionType, accessType, "", request.UserId) - if err != nil || flag == false { - return nil, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, "") - if err != nil { - return nil, err - } - if roleModel.Id == 0 { - request.Status = fmt.Sprintf("%s+%s,%s,%s,%s", bean2.RoleNotFoundStatusPrefix, roleFilter.Team, environment, entityName, actionType) + for _, subaction := range subActions { + otherEntityRoleFieldDto := adapter.BuildOtherRoleFieldsDto(entity, roleFilter.Team, entityName, environment, actionType, accessType, false, subaction, false) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(otherEntityRoleFieldDto) + if err != nil { + impl.logger.Errorw("error in getting new role model") + return nil, err + } + if roleModel.Id == 0 { + if roleFilter.Entity == bean2.ENTITY_APPS || roleFilter.Entity == bean2.CHART_GROUP_ENTITY { + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(otherEntityRoleFieldDto, userLoggedInId) + if err != nil || flag == false { + return nil, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(otherEntityRoleFieldDto) + if err != nil { + return nil, err + } + if roleModel.Id == 0 { + continue + } + } else { continue } - } else { - continue } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which are removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) - } else { - if roleModel.Id > 0 { - roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} - roleGroupMappingModel.CreatedBy = request.UserId - roleGroupMappingModel.UpdatedBy = request.UserId - roleGroupMappingModel.CreatedOn = time.Now() - roleGroupMappingModel.UpdatedOn = time.Now() - roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) - if err != nil { - return nil, err + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which are removed + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) + } else { + if roleModel.Id > 0 { + roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} + roleGroupMappingModel.AuditLog = sql.NewDefaultAuditLog(userLoggedInId) + roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + if err != nil { + return nil, err + } + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) } } } @@ -318,51 +355,56 @@ func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForOtherEntity(roleFilte return policiesToBeAdded, nil } -func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForJobsEntity(roleFilter bean.RoleFilter, userId int32, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]casbin2.Policy, error) { +func (impl RoleGroupServiceImpl) CreateOrUpdateRoleGroupForJobsEntity(roleFilter bean2.RoleFilter, userId int32, model *repository.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, capacity int) ([]bean3.Policy, error) { actionType := roleFilter.Action accessType := roleFilter.AccessType entity := roleFilter.Entity entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") workflows := strings.Split(roleFilter.Workflow, ",") - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + var policiesToBeAdded = make([]bean3.Policy, 0, capacity) for _, environment := range environments { for _, entityName := range entityNames { for _, workflow := range workflows { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, workflow) - if err != nil { - impl.logger.Errorw("error in getting new role model") - return nil, err - } - if roleModel.Id == 0 { - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(roleFilter.Team, entityName, environment, entity, "", "", "", "", "", actionType, accessType, workflow, userId) - if err != nil || flag == false { - return nil, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, workflow) + for _, subaction := range subActions { + jobRoleFieldDto := adapter.BuildJobsRoleFieldsDto(entity, roleFilter.Team, entityName, environment, actionType, accessType, workflow, subaction) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(jobRoleFieldDto) if err != nil { + impl.logger.Errorw("error in getting new role model") return nil, err } if roleModel.Id == 0 { - continue - } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which are removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) - } else { - if roleModel.Id > 0 { - roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} - roleGroupMappingModel.CreatedBy = userId - roleGroupMappingModel.UpdatedBy = userId - roleGroupMappingModel.CreatedOn = time.Now() - roleGroupMappingModel.UpdatedOn = time.Now() - roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(jobRoleFieldDto, userId) + if err != nil || flag == false { + return nil, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(jobRoleFieldDto) if err != nil { return nil, err } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.CasbinName), Obj: casbin2.Object(roleModel.Role)}) + if roleModel.Id == 0 { + continue + } + } + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which are removed + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) + } else { + if roleModel.Id > 0 { + roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: model.Id, RoleId: roleModel.Id} + roleGroupMappingModel.CreatedBy = userId + roleGroupMappingModel.UpdatedBy = userId + roleGroupMappingModel.CreatedOn = time.Now() + roleGroupMappingModel.UpdatedOn = time.Now() + roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) + if err != nil { + return nil, err + } + policiesToBeAdded = append(policiesToBeAdded, adapter.GetCasbinGroupPolicy(model.CasbinName, roleModel.Role, nil)) + } } } } @@ -388,8 +430,8 @@ func (impl RoleGroupServiceImpl) checkIfRoleGroupSuperAdmin(casbinName string) ( } -func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean.RoleGroup, token string, checkRBACForGroupUpdate func(token string, groupInfo *bean.RoleGroup, - eliminatedRoleFilters []*repository.RoleModel, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean2.RoleGroup, token string, checkRBACForGroupUpdate func(token string, groupInfo *bean2.RoleGroup, + eliminatedRoleFilters []*repository.RoleModel, isRoleGroupAlreadySuperAdmin bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean2.RoleGroup, error) { dbConnection := impl.roleGroupRepository.GetConnection() tx, err := dbConnection.Begin() if err != nil { @@ -422,83 +464,26 @@ func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean.RoleGroup, token } //loading policy for safety casbin2.LoadPolicy() - existingRoles := make(map[int]*repository.RoleGroupRoleMapping) - eliminatedRoles := make(map[int]*repository.RoleGroupRoleMapping) - var eliminatedPolicies []casbin2.Policy - capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(request.RoleFilters) - var policies = make([]casbin2.Policy, 0, capacity) + var eliminatedPolicies []bean3.Policy + var policies = make([]bean3.Policy, 0) var eliminatedRoleModels []*repository.RoleModel - var items []casbin2.Policy - if request.SuperAdmin == false { - roleGroupMappingModels, err := impl.roleGroupRepository.GetRoleGroupRoleMappingByRoleGroupId(roleGroup.Id) - if err != nil { - return nil, err - } - - for _, item := range roleGroupMappingModels { - existingRoles[item.RoleId] = item - eliminatedRoles[item.RoleId] = item - } - - // DELETE PROCESS STARTS - items, eliminatedRoleModels, err = impl.userCommonService.RemoveRolesAndReturnEliminatedPoliciesForGroups(request, existingRoles, eliminatedRoles, tx, token, managerAuth) + if request.SuperAdmin { + policiesToBeAdded, err := impl.CreateAndAddPolicesForSuperAdmin(tx, request.UserId, roleGroup.Id, roleGroup.CasbinName) if err != nil { + impl.logger.Errorw("error encountered in UpdateRoleGroup", "error", err, "roleGroupId", roleGroup.Id) return nil, err } - eliminatedPolicies = append(eliminatedPolicies, items...) - // DELETE PROCESS ENDS - - //Adding New Policies - for index, roleFilter := range request.RoleFilters { - if roleFilter.Entity == bean2.CLUSTER_ENTITIY { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForClusterEntity(roleFilter, request.UserId, roleGroup, existingRoles, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - impl.logger.Errorw("error in creating updating role group for cluster entity", "err", err, "roleFilter", roleFilter) - } - } else { - switch roleFilter.Entity { - case bean2.EntityJobs: - { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForJobsEntity(roleFilter, request.UserId, roleGroup, existingRoles, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - impl.logger.Errorw("error in creating updating role group for jobs entity", "err", err, "roleFilter", roleFilter) - } - } - default: - { - policiesToBeAdded, err := impl.CreateOrUpdateRoleGroupForOtherEntity(roleFilter, request, roleGroup, existingRoles, tx, mapping[index]) - policies = append(policies, policiesToBeAdded...) - if err != nil { - impl.logger.Errorw("error in creating updating role group for other entity", "err", err, "roleFilter", roleFilter) - } - } - } - } - } - } else if request.SuperAdmin == true { - flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, request.UserId) - if err != nil || flag == false { - impl.logger.Errorw("error in CreateRoleForSuperAdminIfNotExists ", "err", err, "roleGroupName", request.Name) - return nil, err - } - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes("", "", "", "", bean2.SUPER_ADMIN, "", "", "", "", "", "", "", false, "") + policies = append(policies, policiesToBeAdded...) + } else { + var policiesToBeAdded, policiesToBeEliminated []bean3.Policy + policiesToBeAdded, policiesToBeEliminated, eliminatedRoleModels, err = impl.UpdateAndAddPoliciesForNonSuperAdmin(tx, request, roleGroup, token, managerAuth) if err != nil { - impl.logger.Errorw("error in getting role by filter for all Types for superAdmin", "err", err) + impl.logger.Errorw("error encountered in UpdateRoleGroup", "error", err, "roleGroupId", roleGroup.Id) return nil, err } - if roleModel.Id > 0 { - roleGroupMappingModel := &repository.RoleGroupRoleMapping{RoleGroupId: roleGroup.Id, RoleId: roleModel.Id} - roleGroupMappingModel.CreateAuditLog(request.UserId) - roleGroupMappingModel, err = impl.roleGroupRepository.CreateRoleGroupRoleMapping(roleGroupMappingModel, tx) - if err != nil { - impl.logger.Errorw("error in creating role group role mapping", "err", err, "RoleGroupId", roleGroup.Id) - return nil, err - } - policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(roleGroup.CasbinName), Obj: casbin2.Object(roleModel.Role)}) - } + policies = append(policies, policiesToBeAdded...) + eliminatedPolicies = append(eliminatedPolicies, policiesToBeEliminated...) } if checkRBACForGroupUpdate != nil { @@ -540,6 +525,46 @@ func (impl RoleGroupServiceImpl) UpdateRoleGroup(request *bean.RoleGroup, token return request, nil } +func (impl RoleGroupServiceImpl) UpdateAndAddPoliciesForNonSuperAdmin(tx *pg.Tx, request *bean2.RoleGroup, roleGroup *repository.RoleGroup, token string, managerAuth func(resource string, token string, object string) bool) ([]bean3.Policy, []bean3.Policy, []*repository.RoleModel, error) { + var eliminatedPolicies []bean3.Policy + capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(request.RoleFilters) + var policies = make([]bean3.Policy, 0, capacity) + var eliminatedRoleModels []*repository.RoleModel + + roleGroupMappingModels, err := impl.roleGroupRepository.GetRoleGroupRoleMappingByRoleGroupId(roleGroup.Id) + if err != nil { + return nil, nil, nil, err + } + existingRoles := make(map[int]*repository.RoleGroupRoleMapping) + eliminatedRoles := make(map[int]*repository.RoleGroupRoleMapping) + for _, item := range roleGroupMappingModels { + existingRoles[item.RoleId] = item + eliminatedRoles[item.RoleId] = item + } + + // DELETE PROCESS STARTS + + eliminatedPolicies, eliminatedRoleModels, err = impl.userCommonService.RemoveRolesAndReturnEliminatedPoliciesForGroups(request, existingRoles, eliminatedRoles, tx, token, managerAuth) + if err != nil { + impl.logger.Errorw("error encountered in UpdateAndAddPoliciesForNonSuperAdmin", "err", err) + return nil, nil, nil, err + } + // DELETE PROCESS ENDS + + //Adding New Policies + for index, roleFilter := range request.RoleFilters { + entity := roleFilter.Entity + policiesToBeAdded, err := impl.createOrUpdateRoleGroupRoleMappingForAllTypes(tx, roleFilter, roleGroup, existingRoles, entity, mapping[index], request.UserId) + if err != nil { + impl.logger.Errorw("error encountered in UpdateAndAddPoliciesForNonSuperAdmin", "err", err) + return nil, nil, nil, err + } + policies = append(policies, policiesToBeAdded...) + + } + return policies, eliminatedPolicies, eliminatedRoleModels, nil +} + const ( AllEnvironment string = "" AllNamespace string = "" @@ -549,15 +574,19 @@ const ( AllWorkflow string = "" ) -func (impl RoleGroupServiceImpl) FetchRoleGroupsById(id int32) (*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) FetchRoleGroupsById(id int32) (*bean2.RoleGroup, error) { roleGroup, err := impl.roleGroupRepository.GetRoleGroupById(id) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - roleFilters, superAdmin := impl.getRoleGroupMetadata(roleGroup) - bean := &bean.RoleGroup{ + roleFilters, superAdmin, err := impl.getRoleGroupMetadata(roleGroup) + if err != nil { + impl.logger.Errorw("error encountered in FetchRoleGroupsById", "err", err) + return nil, err + } + bean := &bean2.RoleGroup{ Id: roleGroup.Id, Name: roleGroup.Name, Description: roleGroup.Description, @@ -567,33 +596,47 @@ func (impl RoleGroupServiceImpl) FetchRoleGroupsById(id int32) (*bean.RoleGroup, return bean, nil } -func (impl RoleGroupServiceImpl) getRoleGroupMetadata(roleGroup *repository.RoleGroup) ([]bean.RoleFilter, bool) { +func (impl RoleGroupServiceImpl) getRoleGroupMetadata(roleGroup *repository.RoleGroup) ([]bean2.RoleFilter, bool, error) { roles, err := impl.userAuthRepository.GetRolesByGroupId(roleGroup.Id) if err != nil { impl.logger.Errorw("No Roles Found for user", "roleGroupId", roleGroup.Id) + return nil, false, err } - var roleFilters []bean.RoleFilter + var roleFilters []bean2.RoleFilter isSuperAdmin := helper2.CheckIfSuperAdminFromRoles(roles) // merging considering base as env first roleFilters = impl.userCommonService.BuildRoleFiltersAfterMerging(ConvertRolesToEntityProcessors(roles), bean2.EnvironmentBasedKey) // merging role filters based on application now, first took env as base merged, now application as base , merged roleFilters = impl.userCommonService.BuildRoleFiltersAfterMerging(ConvertRoleFiltersToEntityProcessors(roleFilters), bean2.ApplicationBasedKey) if len(roleFilters) == 0 { - roleFilters = make([]bean.RoleFilter, 0) + roleFilters = make([]bean2.RoleFilter, 0) } - return roleFilters, isSuperAdmin + return roleFilters, isSuperAdmin, nil } -func (impl RoleGroupServiceImpl) FetchDetailedRoleGroups(req *bean.ListingRequest) ([]*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) FetchDetailedRoleGroups(req *bean2.ListingRequest) ([]*bean2.RoleGroup, error) { query, queryParams := helper.GetQueryForGroupListingWithFilters(req) roleGroups, err := impl.roleGroupRepository.GetAllExecutingQuery(query, queryParams) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - var list []*bean.RoleGroup + list, err := impl.populateDetailedRoleGroupFromModel(roleGroups, false) + if err != nil { + impl.logger.Errorw("error encountered in FetchDetailedRoleGroups", "err", err) + return nil, err + } + return list, nil +} + +func (impl RoleGroupServiceImpl) populateDetailedRoleGroupFromModel(roleGroups []*repository.RoleGroup, hidePermissions bool) ([]*bean2.RoleGroup, error) { + var list []*bean2.RoleGroup for _, roleGroup := range roleGroups { - roleFilters, isSuperAdmin := impl.getRoleGroupMetadata(roleGroup) + roleFilters, isSuperAdmin, err := impl.getRoleGroupMetadata(roleGroup) + if err != nil { + impl.logger.Errorw("error encountered in FetchDetailedRoleGroups", "err", err) + return nil, err + } for index, roleFilter := range roleFilters { if roleFilter.Entity == "" { roleFilters[index].Entity = bean2.ENTITY_APPS @@ -602,52 +645,55 @@ func (impl RoleGroupServiceImpl) FetchDetailedRoleGroups(req *bean.ListingReques roleFilters[index].AccessType = bean2.DEVTRON_APP } } - roleGrp := &bean.RoleGroup{ + roleGrp := &bean2.RoleGroup{ Id: roleGroup.Id, Name: roleGroup.Name, Description: roleGroup.Description, - RoleFilters: roleFilters, SuperAdmin: isSuperAdmin, + RoleFilters: roleFilters, + } + if hidePermissions { + HidePermissions(roleGrp) } list = append(list, roleGrp) } if len(list) == 0 { - list = make([]*bean.RoleGroup, 0) + list = make([]*bean2.RoleGroup, 0) } return list, nil } -func (impl RoleGroupServiceImpl) FetchRoleGroups() ([]*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) FetchRoleGroups() ([]*bean2.RoleGroup, error) { roleGroup, err := impl.roleGroupRepository.GetAllRoleGroup() if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - var list []*bean.RoleGroup + var list []*bean2.RoleGroup for _, item := range roleGroup { - bean := &bean.RoleGroup{ + bean := &bean2.RoleGroup{ Id: item.Id, Name: item.Name, Description: item.Description, - RoleFilters: make([]bean.RoleFilter, 0), + RoleFilters: make([]bean2.RoleFilter, 0), } list = append(list, bean) } if len(list) == 0 { - list = make([]*bean.RoleGroup, 0) + list = make([]*bean2.RoleGroup, 0) } return list, nil } -func (impl RoleGroupServiceImpl) FetchRoleGroupsV2(req *bean.ListingRequest) (*bean.RoleGroupListingResponse, error) { +func (impl RoleGroupServiceImpl) FetchRoleGroupsV2(req *bean2.ListingRequest) (*bean2.RoleGroupListingResponse, error) { list, err := impl.FetchDetailedRoleGroups(req) if err != nil { impl.logger.Errorw("error in FetchDetailedRoleGroups", "err", err) return nil, err } - response := &bean.RoleGroupListingResponse{ + response := &bean2.RoleGroupListingResponse{ RoleGroups: list, TotalCount: len(list), } @@ -655,7 +701,7 @@ func (impl RoleGroupServiceImpl) FetchRoleGroupsV2(req *bean.ListingRequest) (*b } // FetchRoleGroupsWithFilters takes listing request as input and outputs RoleGroupListingResponse based on the request filters. -func (impl RoleGroupServiceImpl) FetchRoleGroupsWithFilters(request *bean.ListingRequest) (*bean.RoleGroupListingResponse, error) { +func (impl RoleGroupServiceImpl) FetchRoleGroupsWithFilters(request *bean2.ListingRequest) (*bean2.RoleGroupListingResponse, error) { // default values will be used if not provided impl.userCommonService.SetDefaultValuesIfNotPresent(request, true) if request.ShowAll { @@ -680,46 +726,36 @@ func (impl RoleGroupServiceImpl) FetchRoleGroupsWithFilters(request *bean.Listin return nil, err } - response := impl.fetchRoleGroupResponseFromModel(roleGroup, totalCount) - return response, nil + return impl.fetchRoleGroupResponseFromModel(roleGroup, totalCount) } -func (impl RoleGroupServiceImpl) fetchRoleGroupResponseFromModel(roleGroup []*repository.RoleGroup, totalCount int) *bean.RoleGroupListingResponse { - var list []*bean.RoleGroup - for _, item := range roleGroup { - bean := &bean.RoleGroup{ - Id: item.Id, - Name: item.Name, - Description: item.Description, - RoleFilters: make([]bean.RoleFilter, 0), - } - list = append(list, bean) - } - - if len(list) == 0 { - list = make([]*bean.RoleGroup, 0) +func (impl RoleGroupServiceImpl) fetchRoleGroupResponseFromModel(roleGroup []*repository.RoleGroup, totalCount int) (*bean2.RoleGroupListingResponse, error) { + list, err := impl.populateDetailedRoleGroupFromModel(roleGroup, true) + if err != nil { + impl.logger.Errorw("error encountered in fetchRoleGroupResponseFromModel", "err", err) + return nil, err } - response := &bean.RoleGroupListingResponse{ + response := &bean2.RoleGroupListingResponse{ RoleGroups: list, TotalCount: totalCount, } - return response + return response, nil } -func (impl RoleGroupServiceImpl) FetchRoleGroupsByName(name string) ([]*bean.RoleGroup, error) { +func (impl RoleGroupServiceImpl) FetchRoleGroupsByName(name string) ([]*bean2.RoleGroup, error) { roleGroup, err := impl.roleGroupRepository.GetRoleGroupListByName(name) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - var list []*bean.RoleGroup + var list []*bean2.RoleGroup for _, item := range roleGroup { - bean := &bean.RoleGroup{ + bean := &bean2.RoleGroup{ Id: item.Id, Name: item.Name, Description: item.Description, - RoleFilters: make([]bean.RoleFilter, 0), + RoleFilters: make([]bean2.RoleFilter, 0), } list = append(list, bean) @@ -727,7 +763,7 @@ func (impl RoleGroupServiceImpl) FetchRoleGroupsByName(name string) ([]*bean.Rol return list, nil } -func (impl RoleGroupServiceImpl) DeleteRoleGroup(bean *bean.RoleGroup) (bool, error) { +func (impl RoleGroupServiceImpl) DeleteRoleGroup(bean *bean2.RoleGroup) (bool, error) { dbConnection := impl.roleGroupRepository.GetConnection() tx, err := dbConnection.Begin() @@ -796,7 +832,7 @@ func (impl RoleGroupServiceImpl) DeleteRoleGroup(bean *bean.RoleGroup) (bool, er } // BulkDeleteRoleGroups takes in bulk delete request and return error -func (impl RoleGroupServiceImpl) BulkDeleteRoleGroups(request *bean.BulkDeleteRequest) (bool, error) { +func (impl RoleGroupServiceImpl) BulkDeleteRoleGroups(request *bean2.BulkDeleteRequest) (bool, error) { // it handles ListingRequest if filters are applied will delete those users or will consider the given user ids. if request.ListingRequest != nil { filteredGroupIds, err := impl.getGroupIdsHonoringFilters(request.ListingRequest) @@ -817,7 +853,7 @@ func (impl RoleGroupServiceImpl) BulkDeleteRoleGroups(request *bean.BulkDeleteRe } // getGroupIdsHonoringFilters get the filtered group ids according to the request filters and returns groupIds and error(not nil) if any exception is caught. -func (impl *RoleGroupServiceImpl) getGroupIdsHonoringFilters(request *bean.ListingRequest) ([]int32, error) { +func (impl *RoleGroupServiceImpl) getGroupIdsHonoringFilters(request *bean2.ListingRequest) ([]int32, error) { //query to get particular models respecting filters query, queryParams := helper.GetQueryForGroupListingWithFilters(request) models, err := impl.roleGroupRepository.GetAllExecutingQuery(query, queryParams) @@ -834,7 +870,7 @@ func (impl *RoleGroupServiceImpl) getGroupIdsHonoringFilters(request *bean.Listi } // deleteRoleGroupsByIds delete role groups by ids takes in bulk delete request and return error -func (impl RoleGroupServiceImpl) deleteRoleGroupsByIds(request *bean.BulkDeleteRequest) error { +func (impl RoleGroupServiceImpl) deleteRoleGroupsByIds(request *bean2.BulkDeleteRequest) error { tx, err := impl.roleGroupRepository.StartATransaction() if err != nil { impl.logger.Errorw("error in starting a transaction", "err", err) @@ -932,7 +968,7 @@ func (impl RoleGroupServiceImpl) deleteMappingsFromCasbin(groupCasbinNames []str return nil } -func (impl RoleGroupServiceImpl) FetchRolesForUserRoleGroups(userRoleGroups []bean.UserRoleGroup) ([]*bean.RoleFilter, error) { +func (impl RoleGroupServiceImpl) FetchRolesForUserRoleGroups(userRoleGroups []bean2.UserRoleGroup) ([]*bean2.RoleFilter, error) { groupNames := make([]string, 0) for _, userRoleGroup := range userRoleGroups { groupNames = append(groupNames, userRoleGroup.RoleGroup.Name) @@ -960,12 +996,12 @@ func (impl RoleGroupServiceImpl) FetchRolesForUserRoleGroups(userRoleGroups []be impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - list := make([]*bean.RoleFilter, 0) + list := make([]*bean2.RoleFilter, 0) if roles == nil { return list, nil } for _, role := range roles { - bean := &bean.RoleFilter{ + bean := &bean2.RoleFilter{ EntityName: role.EntityName, Entity: role.Entity, Action: role.Action, @@ -983,3 +1019,19 @@ func (impl RoleGroupServiceImpl) FetchRolesForUserRoleGroups(userRoleGroups []be } return list, nil } + +func (impl RoleGroupServiceImpl) GetGroupIdVsRoleGroupMapForIds(ids []int32) (map[int32]*repository.RoleGroup, error) { + groupIdVsRoleGroupMap := make(map[int32]*repository.RoleGroup) + if len(ids) > 0 { + roleGroups, err := impl.roleGroupRepository.GetRoleGroupListByIds(ids) + if err != nil { + impl.logger.Errorw("error in GetRoleIdVsRoleGroupMapForIds", "ids", ids, "err", err) + return nil, err + } + for _, group := range roleGroups { + groupIdVsRoleGroupMap[group.Id] = group + } + } + return groupIdVsRoleGroupMap, nil + +} diff --git a/pkg/auth/user/RoleGroupService_ent.go b/pkg/auth/user/RoleGroupService_ent.go new file mode 100644 index 0000000000..3ef6872602 --- /dev/null +++ b/pkg/auth/user/RoleGroupService_ent.go @@ -0,0 +1,10 @@ +package user + +import ( + "github.com/devtron-labs/devtron/pkg/auth/user/bean" +) + +func HidePermissions(roleGroup *bean.RoleGroup) { + // setting empty role filters to hide permissions + roleGroup.RoleFilters = make([]bean.RoleFilter, 0) +} diff --git a/pkg/auth/user/UserAuthService.go b/pkg/auth/user/UserAuthService.go index 33416e3160..4e735dbd35 100644 --- a/pkg/auth/user/UserAuthService.go +++ b/pkg/auth/user/UserAuthService.go @@ -37,7 +37,6 @@ import ( "github.com/caarlos0/env" "github.com/coreos/go-oidc/v3/oidc" - "github.com/devtron-labs/devtron/api/bean" session2 "github.com/devtron-labs/devtron/client/argocdServer/session" "github.com/devtron-labs/devtron/internal/constants" "github.com/devtron-labs/devtron/internal/util" @@ -53,7 +52,7 @@ type UserAuthService interface { HandleDexCallback(w http.ResponseWriter, r *http.Request) HandleRefresh(w http.ResponseWriter, r *http.Request) - CreateRole(roleData *bean.RoleData) (bool, error) + CreateRole(roleData *userBean.RoleData) (bool, error) AuthVerification(r *http.Request) (bool, string, error) DeleteRoles(entityType string, entityName string, tx *pg.Tx, envIdentifier string, workflowName string) error } @@ -433,7 +432,7 @@ func writeResponse(status int, message string, w http.ResponseWriter, err error) } } -func (impl UserAuthServiceImpl) CreateRole(roleData *bean.RoleData) (bool, error) { +func (impl UserAuthServiceImpl) CreateRole(roleData *userBean.RoleData) (bool, error) { roleModel := &repository.RoleModel{ Role: roleData.Role, Team: roleData.Team, diff --git a/pkg/auth/user/UserCommonService.go b/pkg/auth/user/UserCommonService.go index 6b370708b8..7f5b0961e4 100644 --- a/pkg/auth/user/UserCommonService.go +++ b/pkg/auth/user/UserCommonService.go @@ -18,6 +18,9 @@ package user import ( "fmt" + bean3 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/adapter" + "github.com/devtron-labs/devtron/pkg/auth/user/repository/bean" "golang.org/x/exp/maps" "math" "strings" @@ -25,7 +28,6 @@ import ( "github.com/caarlos0/env/v6" "github.com/devtron-labs/authenticator/middleware" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository" @@ -36,17 +38,17 @@ import ( ) type UserCommonService interface { - CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string, userId int32) (bool, error, []casbin.Policy) - RemoveRolesAndReturnEliminatedPolicies(userInfo *bean.UserInfo, existingRoleIds map[int]repository.UserRoleModel, eliminatedRoleIds map[int]*repository.UserRoleModel, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]casbin.Policy, []*repository.RoleModel, error) - RemoveRolesAndReturnEliminatedPoliciesForGroups(request *bean.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, eliminatedRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]casbin.Policy, []*repository.RoleModel, error) + CreateDefaultPoliciesForAllTypes(roleFieldsDto *bean.RoleModelFieldsDto, userId int32) (bool, error, []bean3.Policy) + RemoveRolesAndReturnEliminatedPolicies(userInfo *bean2.UserInfo, existingRoleIds map[int]repository.UserRoleModel, eliminatedRoleIds map[int]*repository.UserRoleModel, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]bean3.Policy, []*repository.RoleModel, error) + RemoveRolesAndReturnEliminatedPoliciesForGroups(request *bean2.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, eliminatedRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]bean3.Policy, []*repository.RoleModel, error) CheckRbacForClusterEntity(cluster, namespace, group, kind, resource, token string, managerAuth func(resource, token, object string) bool) bool - GetCapacityForRoleFilter(roleFilters []bean.RoleFilter) (int, map[int]int) - BuildRoleFilterForAllTypes(roleFilterMap map[string]*bean.RoleFilter, entityProcessor EntityKeyProcessor, key string, basetoConsider bean2.MergingBaseKey) + GetCapacityForRoleFilter(roleFilters []bean2.RoleFilter) (int, map[int]int) + BuildRoleFilterForAllTypes(roleFilterMap map[string]*bean2.RoleFilter, entityProcessor EntityKeyProcessor, key string, basetoConsider bean2.MergingBaseKey) GetUniqueKeyForAllEntity(entityProcessor EntityKeyProcessor, baseToConsider bean2.MergingBaseKey) string - SetDefaultValuesIfNotPresent(request *bean.ListingRequest, isRoleGroup bool) + SetDefaultValuesIfNotPresent(request *bean2.ListingRequest, isRoleGroup bool) DeleteRoleForUserFromCasbin(mappings map[string][]string) bool DeleteUserForRoleFromCasbin(mappings map[string][]string) bool - BuildRoleFiltersAfterMerging(entityProcessors []EntityKeyProcessor, baseKey bean2.MergingBaseKey) []bean.RoleFilter + BuildRoleFiltersAfterMerging(entityProcessors []EntityKeyProcessor, baseKey bean2.MergingBaseKey) []bean2.RoleFilter } // Defining a common interface for role and rolefilter @@ -106,7 +108,11 @@ type UserRbacConfig struct { UseRbacCreationV2 bool `env:"USE_RBAC_CREATION_V2" envDefault:"true"` } -func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string, userId int32) (bool, error, []casbin.Policy) { +func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypes(roleFieldsDto *bean.RoleModelFieldsDto, userId int32) (bool, error, []bean3.Policy) { + team, entityName, env, entity, cluster, namespace, group, kind, + resource, actionType, accessType, workflow := roleFieldsDto.Team, roleFieldsDto.App, roleFieldsDto.Env, + roleFieldsDto.Entity, roleFieldsDto.Cluster, roleFieldsDto.Namespace, roleFieldsDto.Group, + roleFieldsDto.Kind, roleFieldsDto.Resource, roleFieldsDto.Action, roleFieldsDto.AccessType, roleFieldsDto.Workflow if impl.userRbacConfig.UseRbacCreationV2 { impl.logger.Debugw("using rbac creation v2 for creating default policies") return impl.CreateDefaultPoliciesForAllTypesV2(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow) @@ -115,7 +121,7 @@ func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypes(team, entityN } } -func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypesV2(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string) (bool, error, []casbin.Policy) { +func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypesV2(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string) (bool, error, []bean3.Policy) { //TODO: below txn is making this process slow, need to do bulk operation for role creation. //For detail - https://github.com/devtron-labs/devtron/blob/main/pkg/user/benchmarking-results @@ -130,7 +136,7 @@ func (impl UserCommonServiceImpl) CreateDefaultPoliciesForAllTypesV2(team, entit return true, nil, renderedPolicyDetails } -func (impl UserCommonServiceImpl) getRenderedRoleAndPolicy(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string) (*repository.RoleModel, []casbin.Policy, error) { +func (impl UserCommonServiceImpl) getRenderedRoleAndPolicy(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType, workflow string) (*repository.RoleModel, []bean3.Policy, error) { //getting map of values to be used for rendering pValUpdateMap := getPValUpdateMap(team, entityName, env, entity, cluster, namespace, group, kind, resource, workflow) @@ -175,20 +181,20 @@ func getRenderedRoleData(defaultRoleData repository.RoleCacheDetailObj, pValUpda return renderedRoleData } -func getRenderedPolicy(defaultPolicy repository.PolicyCacheDetailObj, pValUpdateMap map[repository.PValUpdateKey]string) []casbin.Policy { - renderedPolicies := make([]casbin.Policy, 0, len(defaultPolicy.ResActObjSet)) +func getRenderedPolicy(defaultPolicy repository.PolicyCacheDetailObj, pValUpdateMap map[repository.PValUpdateKey]string) []bean3.Policy { + renderedPolicies := make([]bean3.Policy, 0, len(defaultPolicy.ResActObjSet)) policyType := getResolvedValueFromPValDetailObject(defaultPolicy.Type, pValUpdateMap) policySub := getResolvedValueFromPValDetailObject(defaultPolicy.Sub, pValUpdateMap) for _, v := range defaultPolicy.ResActObjSet { policyRes := getResolvedValueFromPValDetailObject(v.Res, pValUpdateMap) policyAct := getResolvedValueFromPValDetailObject(v.Act, pValUpdateMap) policyObj := getResolvedValueFromPValDetailObject(v.Obj, pValUpdateMap) - renderedPolicy := casbin.Policy{ - Type: casbin.PolicyType(policyType), - Sub: casbin.Subject(policySub), - Res: casbin.Resource(policyRes), - Act: casbin.Action(policyAct), - Obj: casbin.Object(policyObj), + renderedPolicy := bean3.Policy{ + Type: bean3.PolicyType(policyType), + Sub: bean3.Subject(policySub), + Res: bean3.Resource(policyRes), + Act: bean3.Action(policyAct), + Obj: bean3.Object(policyObj), } renderedPolicies = append(renderedPolicies, renderedPolicy) } @@ -251,10 +257,10 @@ func getResolvedPValMapValue(rawValue string) string { return resolvedVal } -func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInfo *bean.UserInfo, +func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInfo *bean2.UserInfo, existingRoleIds map[int]repository.UserRoleModel, eliminatedRoleIds map[int]*repository.UserRoleModel, - tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]casbin.Policy, []*repository.RoleModel, error) { - var eliminatedPolicies []casbin.Policy + tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]bean3.Policy, []*repository.RoleModel, error) { + var eliminatedPolicies []bean3.Policy // DELETE Removed Items for _, roleFilter := range userInfo.RoleFilters { if roleFilter.Entity == bean2.CLUSTER_ENTITIY { @@ -264,17 +270,49 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInf resources := strings.Split(roleFilter.Resource, ",") accessType := roleFilter.AccessType actionType := roleFilter.Action + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") for _, namespace := range namespaces { for _, group := range groups { for _, kind := range kinds { for _, resource := range resources { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildClusterRoleFieldsDto(roleFilter.Entity, accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, subaction)) + if err != nil { + impl.logger.Errorw("Error in fetching roles by filter", "roleFilter", roleFilter) + return nil, nil, err + } + if roleModel.Id == 0 { + impl.logger.Warnw("no role found for given filter", "filter", roleFilter) + continue + } + if _, ok := existingRoleIds[roleModel.Id]; ok { + delete(eliminatedRoleIds, roleModel.Id) + } + } + } + } + } + } + } else if roleFilter.Entity == bean2.EntityJobs { + entityNames := strings.Split(roleFilter.EntityName, ",") + environments := strings.Split(roleFilter.Environment, ",") + workflows := strings.Split(roleFilter.Workflow, ",") + actionType := roleFilter.Action + accessType := roleFilter.AccessType + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + for _, environment := range environments { + for _, entityName := range entityNames { + for _, workflow := range workflows { + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildJobsRoleFieldsDto(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, workflow, subaction)) if err != nil { - impl.logger.Errorw("Error in fetching roles by filter", "roleFilter", roleFilter) + impl.logger.Errorw("Error in fetching roles by filter", "user", userInfo) return nil, nil, err } if roleModel.Id == 0 { - impl.logger.Warnw("no role found for given filter", "filter", roleFilter) + impl.logger.Debugw("no role found for given filter", "filter", roleFilter) continue } if _, ok := existingRoleIds[roleModel.Id]; ok { @@ -284,63 +322,40 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInf } } } - } else if roleFilter.Entity == bean2.EntityJobs { + } else { entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") - workflows := strings.Split(roleFilter.Workflow, ",") actionType := roleFilter.Action accessType := roleFilter.AccessType + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") for _, environment := range environments { for _, entityName := range entityNames { - for _, workflow := range workflows { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, workflow) + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildOtherRoleFieldsDto(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, false, subaction, false)) if err != nil { impl.logger.Errorw("Error in fetching roles by filter", "user", userInfo) return nil, nil, err } + oldRoleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildOtherRoleFieldsDto(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, true, subaction, false)) + if err != nil { + return nil, nil, err + } if roleModel.Id == 0 { impl.logger.Debugw("no role found for given filter", "filter", roleFilter) - userInfo.Status = "role not fount for any given filter: " + roleFilter.Team + "," + environment + "," + entityName + "," + roleFilter.Action continue } if _, ok := existingRoleIds[roleModel.Id]; ok { delete(eliminatedRoleIds, roleModel.Id) } + isChartGroupEntity := roleFilter.Entity == bean2.CHART_GROUP_ENTITY + if _, ok := existingRoleIds[oldRoleModel.Id]; ok && !isChartGroupEntity { + //delete old role mapping from existing but not from eliminated roles (so that it gets deleted) + delete(existingRoleIds, oldRoleModel.Id) + } } } } - } else { - entityNames := strings.Split(roleFilter.EntityName, ",") - environments := strings.Split(roleFilter.Environment, ",") - actionType := roleFilter.Action - accessType := roleFilter.AccessType - for _, environment := range environments { - for _, entityName := range entityNames { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, "") - if err != nil { - impl.logger.Errorw("Error in fetching roles by filter", "user", userInfo) - return nil, nil, err - } - oldRoleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, true, "") - if err != nil { - return nil, nil, err - } - if roleModel.Id == 0 { - impl.logger.Debugw("no role found for given filter", "filter", roleFilter) - userInfo.Status = "role not fount for any given filter: " + roleFilter.Team + "," + environment + "," + entityName + "," + roleFilter.Action - continue - } - if _, ok := existingRoleIds[roleModel.Id]; ok { - delete(eliminatedRoleIds, roleModel.Id) - } - isChartGroupEntity := roleFilter.Entity == bean2.CHART_GROUP_ENTITY - if _, ok := existingRoleIds[oldRoleModel.Id]; ok && !isChartGroupEntity { - //delete old role mapping from existing but not from eliminated roles (so that it gets deleted) - delete(existingRoleIds, oldRoleModel.Id) - } - - } - } } } @@ -361,7 +376,7 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPolicies(userInf } toBeDeletedUserRolesIds = append(toBeDeletedUserRolesIds, userRoleModel.Id) eliminatedRoles = append(eliminatedRoles, role) - eliminatedPolicies = append(eliminatedPolicies, casbin.Policy{Type: "g", Sub: casbin.Subject(userInfo.EmailId), Obj: casbin.Object(role.Role)}) + eliminatedPolicies = append(eliminatedPolicies, bean3.Policy{Type: "g", Sub: bean3.Subject(userInfo.EmailId), Obj: bean3.Object(role.Role)}) } } @@ -391,7 +406,7 @@ func (impl UserCommonServiceImpl) getMapOfRoleIdVsRoleForRoleIds(roleIds []int) return roleIdVsRoleMap, nil } -func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroups(request *bean.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, eliminatedRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]casbin.Policy, []*repository.RoleModel, error) { +func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroups(request *bean2.RoleGroup, existingRoles map[int]*repository.RoleGroupRoleMapping, eliminatedRoles map[int]*repository.RoleGroupRoleMapping, tx *pg.Tx, token string, managerAuth func(resource string, token string, object string) bool) ([]bean3.Policy, []*repository.RoleModel, error) { // Filter out removed items in current request //var policies []casbin.Policy for _, roleFilter := range request.RoleFilters { @@ -403,11 +418,43 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroup resources := strings.Split(roleFilter.Resource, ",") actionType := roleFilter.Action accessType := roleFilter.AccessType + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") for _, namespace := range namespaces { for _, group := range groups { for _, kind := range kinds { for _, resource := range resources { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildClusterRoleFieldsDto(entity, accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, subaction)) + if err != nil { + impl.logger.Errorw("Error in fetching roles by filter", "user", request) + return nil, nil, err + } + if roleModel.Id == 0 { + impl.logger.Warnw("no role found for given filter", "filter", roleFilter) + continue + } + if _, ok := existingRoles[roleModel.Id]; ok { + delete(eliminatedRoles, roleModel.Id) + } + } + } + } + } + } + } else if entity == bean2.EntityJobs { + entityNames := strings.Split(roleFilter.EntityName, ",") + environments := strings.Split(roleFilter.Environment, ",") + workflows := strings.Split(roleFilter.Workflow, ",") + accessType := roleFilter.AccessType + actionType := roleFilter.Action + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + for _, environment := range environments { + for _, entityName := range entityNames { + for _, workflow := range workflows { + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildJobsRoleFieldsDto(entity, roleFilter.Team, entityName, environment, actionType, accessType, workflow, subaction)) if err != nil { impl.logger.Errorw("Error in fetching roles by filter", "user", request) return nil, nil, err @@ -423,70 +470,47 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroup } } } - } else if entity == bean2.EntityJobs { + } else { entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") - workflows := strings.Split(roleFilter.Workflow, ",") accessType := roleFilter.AccessType actionType := roleFilter.Action + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") for _, environment := range environments { for _, entityName := range entityNames { - for _, workflow := range workflows { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, workflow) + for _, subaction := range subActions { + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildOtherRoleFieldsDto(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, false, subaction, false)) if err != nil { impl.logger.Errorw("Error in fetching roles by filter", "user", request) return nil, nil, err } - if roleModel.Id == 0 { + oldRoleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildOtherRoleFieldsDto(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, true, subaction, false)) + if err != nil { + impl.logger.Errorw("Error in fetching roles by filter by old values", "user", request) + return nil, nil, err + } + if roleModel.Id == 0 && oldRoleModel.Id == 0 { impl.logger.Warnw("no role found for given filter", "filter", roleFilter) - request.Status = "role not fount for any given filter: " + roleFilter.Team + "," + environment + "," + entityName + "," + actionType continue } if _, ok := existingRoles[roleModel.Id]; ok { delete(eliminatedRoles, roleModel.Id) } + isChartGroupEntity := roleFilter.Entity == bean2.CHART_GROUP_ENTITY + if _, ok := existingRoles[oldRoleModel.Id]; ok && !isChartGroupEntity { + //delete old role mapping from existing but not from eliminated roles (so that it gets deleted) + delete(existingRoles, oldRoleModel.Id) + } } } } - } else { - entityNames := strings.Split(roleFilter.EntityName, ",") - environments := strings.Split(roleFilter.Environment, ",") - accessType := roleFilter.AccessType - actionType := roleFilter.Action - for _, environment := range environments { - for _, entityName := range entityNames { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", false, "") - if err != nil { - impl.logger.Errorw("Error in fetching roles by filter", "user", request) - return nil, nil, err - } - oldRoleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(roleFilter.Entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", "", true, "") - if err != nil { - impl.logger.Errorw("Error in fetching roles by filter by old values", "user", request) - return nil, nil, err - } - if roleModel.Id == 0 && oldRoleModel.Id == 0 { - impl.logger.Warnw("no role found for given filter", "filter", roleFilter) - request.Status = "role not fount for any given filter: " + roleFilter.Team + "," + environment + "," + entityName + "," + actionType - continue - } - if _, ok := existingRoles[roleModel.Id]; ok { - delete(eliminatedRoles, roleModel.Id) - } - isChartGroupEntity := roleFilter.Entity == bean2.CHART_GROUP_ENTITY - if _, ok := existingRoles[oldRoleModel.Id]; ok && !isChartGroupEntity { - //delete old role mapping from existing but not from eliminated roles (so that it gets deleted) - delete(existingRoles, oldRoleModel.Id) - } - - } - } } } //delete remaining Ids from casbin role mapping table in orchestrator and casbin policy db // which are existing but not provided in this request - var eliminatedPolicies []casbin.Policy + var eliminatedPolicies []bean3.Policy eliminatedRoleModels := make([]*repository.RoleModel, 0, len(eliminatedRoles)) toBeDeletedRoleGroupRoleMappingsIds := make([]int, 0, len(eliminatedRoles)) roleIdVsRoleMap, err := impl.getMapOfRoleIdVsRoleForRoleIds(maps.Keys(eliminatedRoles)) @@ -507,7 +531,7 @@ func (impl UserCommonServiceImpl) RemoveRolesAndReturnEliminatedPoliciesForGroup } toBeDeletedRoleGroupRoleMappingsIds = append(toBeDeletedRoleGroupRoleMappingsIds, model.Id) eliminatedRoleModels = append(eliminatedRoleModels, role) - eliminatedPolicies = append(eliminatedPolicies, casbin.Policy{Type: "g", Sub: casbin.Subject(policyGroup.CasbinName), Obj: casbin.Object(role.Role)}) + eliminatedPolicies = append(eliminatedPolicies, bean3.Policy{Type: "g", Sub: bean3.Subject(policyGroup.CasbinName), Obj: bean3.Object(role.Role)}) } } if len(toBeDeletedRoleGroupRoleMappingsIds) > 0 { @@ -537,12 +561,12 @@ func (impl UserCommonServiceImpl) checkRbacForARole(role *repository.RoleModel, isAuthorised = false } - case role.Entity == bean.CLUSTER_ENTITIY: + case role.Entity == bean2.CLUSTER_ENTITIY: isValidAuth := impl.CheckRbacForClusterEntity(role.Cluster, role.Namespace, role.Group, role.Kind, role.Resource, token, managerAuth) if !isValidAuth { isAuthorised = false } - case role.Entity == bean.CHART_GROUP_ENTITY: + case role.Entity == bean2.CHART_GROUP_ENTITY: isAuthorised = true default: isAuthorised = false @@ -604,7 +628,7 @@ func (impl UserCommonServiceImpl) CheckRbacForClusterEntity(cluster, namespace, return true } -func (impl UserCommonServiceImpl) GetCapacityForRoleFilter(roleFilters []bean.RoleFilter) (int, map[int]int) { +func (impl UserCommonServiceImpl) GetCapacityForRoleFilter(roleFilters []bean2.RoleFilter) (int, map[int]int) { capacity := 0 m := make(map[int]int) @@ -623,7 +647,7 @@ func (impl UserCommonServiceImpl) GetCapacityForRoleFilter(roleFilters []bean.Ro return capacity, m } -func (impl UserCommonServiceImpl) BuildRoleFilterForAllTypes(roleFilterMap map[string]*bean.RoleFilter, entityProcessor EntityKeyProcessor, key string, basetoConsider bean2.MergingBaseKey) { +func (impl UserCommonServiceImpl) BuildRoleFilterForAllTypes(roleFilterMap map[string]*bean2.RoleFilter, entityProcessor EntityKeyProcessor, key string, basetoConsider bean2.MergingBaseKey) { switch entityProcessor.GetEntity() { case bean2.CLUSTER_ENTITIY: { @@ -640,7 +664,7 @@ func (impl UserCommonServiceImpl) BuildRoleFilterForAllTypes(roleFilterMap map[s } } -func BuildRoleFilterKeyForCluster(roleFilterMap map[string]*bean.RoleFilter, entityProcessor EntityKeyProcessor, key string) { +func BuildRoleFilterKeyForCluster(roleFilterMap map[string]*bean2.RoleFilter, entityProcessor EntityKeyProcessor, key string) { namespaceArr := strings.Split(roleFilterMap[key].Namespace, ",") if containsArr(namespaceArr, AllNamespace) { roleFilterMap[key].Namespace = AllNamespace @@ -667,7 +691,7 @@ func BuildRoleFilterKeyForCluster(roleFilterMap map[string]*bean.RoleFilter, ent } } -func BuildRoleFilterKeyForJobs(roleFilterMap map[string]*bean.RoleFilter, entityProcessor EntityKeyProcessor, key string, baseToConsider bean2.MergingBaseKey) { +func BuildRoleFilterKeyForJobs(roleFilterMap map[string]*bean2.RoleFilter, entityProcessor EntityKeyProcessor, key string, baseToConsider bean2.MergingBaseKey) { switch baseToConsider { case bean2.ApplicationBasedKey: envArr := strings.Split(roleFilterMap[key].Environment, ",") @@ -705,7 +729,7 @@ func BuildRoleFilterKeyForJobs(roleFilterMap map[string]*bean.RoleFilter, entity } } -func BuildRoleFilterKeyForOtherEntity(roleFilterMap map[string]*bean.RoleFilter, entityProcessor EntityKeyProcessor, key string, baseToConsider bean2.MergingBaseKey) { +func BuildRoleFilterKeyForOtherEntity(roleFilterMap map[string]*bean2.RoleFilter, entityProcessor EntityKeyProcessor, key string, baseToConsider bean2.MergingBaseKey) { switch baseToConsider { case bean2.ApplicationBasedKey: envArr := strings.Split(roleFilterMap[key].Environment, ",") @@ -767,7 +791,7 @@ func (impl UserCommonServiceImpl) GetUniqueKeyForAllEntity(entityProcessor Entit return key } -func (impl UserCommonServiceImpl) SetDefaultValuesIfNotPresent(request *bean.ListingRequest, isRoleGroup bool) { +func (impl UserCommonServiceImpl) SetDefaultValuesIfNotPresent(request *bean2.ListingRequest, isRoleGroup bool) { if len(request.SortBy) == 0 { if isRoleGroup { request.SortBy = bean2.GroupName @@ -810,15 +834,15 @@ func (impl UserCommonServiceImpl) DeleteUserForRoleFromCasbin(mappings map[strin return successful } -func (impl UserCommonServiceImpl) BuildRoleFiltersAfterMerging(entityProcessors []EntityKeyProcessor, baseKey bean2.MergingBaseKey) []bean.RoleFilter { - roleFilterMap := make(map[string]*bean.RoleFilter, len(entityProcessors)) - roleFilters := make([]bean.RoleFilter, 0, len(entityProcessors)) +func (impl UserCommonServiceImpl) BuildRoleFiltersAfterMerging(entityProcessors []EntityKeyProcessor, baseKey bean2.MergingBaseKey) []bean2.RoleFilter { + roleFilterMap := make(map[string]*bean2.RoleFilter, len(entityProcessors)) + roleFilters := make([]bean2.RoleFilter, 0, len(entityProcessors)) for _, entityProcessor := range entityProcessors { key := impl.GetUniqueKeyForAllEntity(entityProcessor, baseKey) if _, ok := roleFilterMap[key]; ok { impl.BuildRoleFilterForAllTypes(roleFilterMap, entityProcessor, key, baseKey) } else { - roleFilterMap[key] = &bean.RoleFilter{ + roleFilterMap[key] = &bean2.RoleFilter{ Entity: entityProcessor.GetEntity(), Team: entityProcessor.GetTeam(), Environment: entityProcessor.GetEnvironment(), @@ -851,7 +875,7 @@ func (impl UserCommonServiceImpl) BuildRoleFiltersAfterMerging(entityProcessors return roleFilters } -func ConvertRoleFiltersToEntityProcessors(filters []bean.RoleFilter) []EntityKeyProcessor { +func ConvertRoleFiltersToEntityProcessors(filters []bean2.RoleFilter) []EntityKeyProcessor { processors := make([]EntityKeyProcessor, len(filters)) for i, filter := range filters { processors[i] = filter // Each RoleFilter is an EntityKeyProcessor diff --git a/pkg/auth/user/UserSelfRegistrationService.go b/pkg/auth/user/UserSelfRegistrationService.go index 68d96bbdc8..671ed723ab 100644 --- a/pkg/auth/user/UserSelfRegistrationService.go +++ b/pkg/auth/user/UserSelfRegistrationService.go @@ -19,7 +19,8 @@ package user import ( "fmt" jwt2 "github.com/devtron-labs/authenticator/jwt" - "github.com/devtron-labs/devtron/api/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/adapter" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository" "github.com/golang-jwt/jwt/v4" "go.uber.org/zap" @@ -105,7 +106,7 @@ func (impl *UserSelfRegistrationServiceImpl) SelfRegister(emailId string) (*bean SuperAdmin: false, } - userInfos, err := impl.userService.SelfRegisterUserIfNotExists(userInfo) + userInfos, err := impl.userService.SelfRegisterUserIfNotExists(adapter.BuildSelfRegisterDto(userInfo)) if err != nil { impl.logger.Errorw("error while register user", "error", err) return nil, err diff --git a/pkg/auth/user/UserService.go b/pkg/auth/user/UserService.go index d6ff19aed5..04a56374dc 100644 --- a/pkg/auth/user/UserService.go +++ b/pkg/auth/user/UserService.go @@ -19,8 +19,10 @@ package user import ( "context" "fmt" + bean4 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" "github.com/devtron-labs/devtron/pkg/auth/user/adapter" userHelper "github.com/devtron-labs/devtron/pkg/auth/user/helper" + adapter2 "github.com/devtron-labs/devtron/pkg/auth/user/repository/adapter" "github.com/devtron-labs/devtron/pkg/auth/user/repository/helper" util3 "github.com/devtron-labs/devtron/pkg/auth/user/util" "net/http" @@ -31,7 +33,6 @@ import ( "github.com/devtron-labs/authenticator/jwt" "github.com/devtron-labs/authenticator/middleware" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/internal/constants" "github.com/devtron-labs/devtron/internal/util" casbin2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" @@ -51,14 +52,14 @@ const ( ) type UserService interface { - CreateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*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, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, error) - GetById(id int32) (*bean.UserInfo, error) - GetAll() ([]bean.UserInfo, error) - GetAllWithFilters(request *bean.ListingRequest) (*bean.UserListingResponse, error) - GetAllDetailedUsers() ([]bean.UserInfo, error) + CreateUser(userInfo *userBean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*userBean.UserInfo, error) + SelfRegisterUserIfNotExists(selfRegisterDto *userBean.SelfRegisterDto) ([]*userBean.UserInfo, error) + UpdateUser(userInfo *userBean.UserInfo, token string, checkRBACForUserUpdate func(token string, userInfo *userBean.UserInfo, isUserAlreadySuperAdmin bool, + eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*userBean.UserInfo, error) + GetByIdWithoutGroupClaims(id int32) (*userBean.UserInfo, error) + GetAll() ([]userBean.UserInfo, error) + GetAllWithFilters(request *userBean.ListingRequest) (*userBean.UserListingResponse, error) + GetAllDetailedUsers() ([]userBean.UserInfo, error) GetEmailFromToken(token string) (string, error) GetEmailAndVersionFromToken(token string) (string, string, error) // GetEmailById returns emailId by userId @@ -72,17 +73,17 @@ type UserService interface { // for audit emails use GetEmailById instead GetActiveEmailById(userId int32) (string, error) GetLoggedInUser(r *http.Request) (int32, error) - GetByIds(ids []int32) ([]bean.UserInfo, error) - DeleteUser(userInfo *bean.UserInfo) (bool, error) - BulkDeleteUsers(request *bean.BulkDeleteRequest) (bool, error) - CheckUserRoles(id int32) ([]string, error) + GetByIds(ids []int32) ([]userBean.UserInfo, error) + DeleteUser(userInfo *userBean.UserInfo) (bool, error) + BulkDeleteUsers(request *userBean.BulkDeleteRequest) (bool, error) + CheckUserRoles(id int32, token string) ([]string, error) SyncOrchestratorToCasbin() (bool, error) GetUserByToken(context context.Context, token string) (int32, string, error) //IsSuperAdmin(userId int) (bool, error) - GetByIdIncludeDeleted(id int32) (*bean.UserInfo, error) + GetByIdIncludeDeleted(id int32) (*userBean.UserInfo, error) UserExists(emailId string) bool UpdateTriggerPolicyForTerminalAccess() (err error) - GetRoleFiltersByUserRoleGroups(userRoleGroups []bean.UserRoleGroup) ([]bean.RoleFilter, error) + GetRoleFiltersByUserRoleGroups(userRoleGroups []userBean.UserRoleGroup) ([]userBean.RoleFilter, error) SaveLoginAudit(emailId, clientIp string, id int32) CheckIfTokenIsValid(email string, version string) error } @@ -99,13 +100,15 @@ type UserServiceImpl struct { sessionManager2 *middleware.SessionManager userCommonService UserCommonService userAuditService UserAuditService + roleGroupService RoleGroupService } func NewUserServiceImpl(userAuthRepository repository.UserAuthRepository, logger *zap.SugaredLogger, userRepository repository.UserRepository, userGroupRepository repository.RoleGroupRepository, - sessionManager2 *middleware.SessionManager, userCommonService UserCommonService, userAuditService UserAuditService) *UserServiceImpl { + sessionManager2 *middleware.SessionManager, userCommonService UserCommonService, userAuditService UserAuditService, + roleGroupService RoleGroupService) *UserServiceImpl { serviceImpl := &UserServiceImpl{ userReqState: make(map[int32]bool), userAuthRepository: userAuthRepository, @@ -115,6 +118,7 @@ func NewUserServiceImpl(userAuthRepository repository.UserAuthRepository, sessionManager2: sessionManager2, userCommonService: userCommonService, userAuditService: userAuditService, + roleGroupService: roleGroupService, } cStore = sessions.NewCookieStore(randKey()) return serviceImpl @@ -150,32 +154,34 @@ func (impl *UserServiceImpl) lockUnlockUserReqState(userId int32, lock bool) err return err } -func (impl *UserServiceImpl) validateUserRequest(userInfo *bean.UserInfo) (bool, error) { +func (impl *UserServiceImpl) validateUserRequest(userInfo *userBean.UserInfo) error { if len(userInfo.RoleFilters) == 1 && userInfo.RoleFilters[0].Team == "" && userInfo.RoleFilters[0].Environment == "" && userInfo.RoleFilters[0].Action == "" { //skip } else { - invalid := false - for _, roleFilter := range userInfo.RoleFilters { - if len(roleFilter.Team) > 0 && len(roleFilter.Action) > 0 { - // - } else if len(roleFilter.Entity) > 0 { //this will pass roleFilter for clusterEntity as well as chart-group - // - } else { - invalid = true - } + err := userHelper.ValidateRoleFilters(userInfo.RoleFilters) + if err != nil { + impl.logger.Errorw("error in validateUserRequest", "err", err) + return err } - if invalid { - err := &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"} - return false, err + err = validateAccessRoleFilters(userInfo) + if err != nil { + impl.logger.Errorw("error in validateUserRequest", "err", err) + return err } } - return true, nil + // validation for checking conflicting user RoleGroups + err := userHelper.ValidateUserRoleGroupRequest(userInfo.UserRoleGroup) + if err != nil { + impl.logger.Errorw("error in validateUserRequest", "err", err) + return err + } + return nil } -func (impl *UserServiceImpl) SelfRegisterUserIfNotExists(userInfo *bean.UserInfo) ([]*bean.UserInfo, error) { - var pass []string - var userResponse []*bean.UserInfo +func (impl *UserServiceImpl) SelfRegisterUserIfNotExists(selfRegisterDto *userBean.SelfRegisterDto) ([]*userBean.UserInfo, error) { + var userResponse []*userBean.UserInfo + userInfo := selfRegisterDto.UserInfo emailIds := strings.Split(userInfo.EmailId, ",") dbConnection := impl.userRepository.GetConnection() tx, err := dbConnection.Begin() @@ -185,7 +191,7 @@ func (impl *UserServiceImpl) SelfRegisterUserIfNotExists(userInfo *bean.UserInfo // Rollback tx on error. defer tx.Rollback() - var policies []casbin2.Policy + var policies []bean4.Policy for _, emailId := range emailIds { dbUser, err := impl.userRepository.FetchActiveOrDeletedUserByEmail(emailId) if err != nil && err != pg.ErrNoRows { @@ -208,29 +214,40 @@ func (impl *UserServiceImpl) SelfRegisterUserIfNotExists(userInfo *bean.UserInfo } return nil, err } - - roles, err := impl.userAuthRepository.GetRoleByRoles(userInfo.Roles) + err = impl.UpdateDataForGroupClaims(selfRegisterDto) if err != nil { - err = &util.ApiError{ - Code: constants.UserCreateDBFailed, - InternalMessage: "configured roles for selfregister are wrong", - UserMessage: fmt.Sprintf("requested by %d", userInfo.UserId), - } + impl.logger.Errorw("error in SelfRegisterUserIfNotExists", "selfRegisterDto", selfRegisterDto, "err", err) return nil, err } - for _, roleModel := range roles { - userRoleModel := &repository.UserRoleModel{UserId: userInfo.Id, RoleId: roleModel.Id} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if len(userInfo.Roles) > 0 { + roles, err := impl.userAuthRepository.GetRoleByRoles(userInfo.Roles) if err != nil { + err = &util.ApiError{ + Code: constants.UserCreateDBFailed, + InternalMessage: "configured roles for selfregister are wrong", + UserMessage: fmt.Sprintf("requested by %d", userInfo.UserId), + } return nil, err } - policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(roleModel.Role)}) + for _, roleModel := range roles { + userRoleModel := &repository.UserRoleModel{UserId: userInfo.Id, RoleId: roleModel.Id} + userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if err != nil { + return nil, err + } + policies = append(policies, bean4.Policy{Type: "g", Sub: bean4.Subject(userInfo.EmailId), Obj: bean4.Object(roleModel.Role)}) + } } - - pass = append(pass, emailId) userInfo.EmailId = emailId userInfo.Exist = dbUser.Active - userResponse = append(userResponse, &bean.UserInfo{Id: userInfo.Id, EmailId: emailId, Groups: userInfo.Groups, RoleFilters: userInfo.RoleFilters, SuperAdmin: userInfo.SuperAdmin}) + userResponseInfo := adapter.BuildUserInfoResponseAdapter(userInfo, emailId) + err = impl.createAuditForSelfRegisterOperation(tx, userResponseInfo) + if err != nil { + impl.logger.Errorw("error in creating audit for user", "err", err, "id", userResponseInfo.Id) + return nil, err + } + + userResponse = append(userResponse, userResponseInfo) } if len(policies) > 0 { @@ -248,7 +265,7 @@ func (impl *UserServiceImpl) SelfRegisterUserIfNotExists(userInfo *bean.UserInfo return userResponse, nil } -func (impl *UserServiceImpl) saveUser(userInfo *bean.UserInfo, emailId string) (*bean.UserInfo, error) { +func (impl *UserServiceImpl) saveUser(userInfo *userBean.UserInfo, emailId string) (*userBean.UserInfo, error) { dbConnection := impl.userRepository.GetConnection() tx, err := dbConnection.Begin() if err != nil { @@ -257,9 +274,9 @@ func (impl *UserServiceImpl) saveUser(userInfo *bean.UserInfo, emailId string) ( // Rollback tx on error. defer tx.Rollback() - _, err = impl.validateUserRequest(userInfo) + err = impl.validateUserRequest(userInfo) if err != nil { - err = &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"} + impl.logger.Errorw("error in saveUser", "request", userInfo, "err", err) return nil, err } @@ -283,13 +300,13 @@ func (impl *UserServiceImpl) saveUser(userInfo *bean.UserInfo, emailId string) ( return nil, err } userInfo.Id = model.Id + userInfo.SetEntityAudit(model.AuditLog) return userInfo, nil } -func (impl *UserServiceImpl) CreateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*bean.UserInfo, error) { - +func (impl *UserServiceImpl) CreateUser(userInfo *userBean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*userBean.UserInfo, error) { var pass []string - var userResponse []*bean.UserInfo + var userResponse []*userBean.UserInfo emailIds := strings.Split(userInfo.EmailId, ",") for _, emailId := range emailIds { dbUser, err := impl.userRepository.FetchActiveOrDeletedUserByEmail(emailId) @@ -319,28 +336,30 @@ func (impl *UserServiceImpl) CreateUser(userInfo *bean.UserInfo, token string, m pass = append(pass, emailId) userInfo.EmailId = emailId userInfo.Exist = dbUser.Active - userResponse = append(userResponse, &bean.UserInfo{Id: userInfo.Id, EmailId: emailId, Groups: userInfo.Groups, RoleFilters: userInfo.RoleFilters, SuperAdmin: userInfo.SuperAdmin, UserRoleGroup: userInfo.UserRoleGroup}) + userResponse = append(userResponse, adapter.BuildUserInfoResponseAdapter(userInfo, emailId)) } return userResponse, nil } -func (impl *UserServiceImpl) updateUserIfExists(userInfo *bean.UserInfo, dbUser *repository.UserModel, emailId string, token string, managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, error) { - updateUserInfo, err := impl.GetById(dbUser.Id) +func (impl *UserServiceImpl) updateUserIfExists(userInfo *userBean.UserInfo, dbUser *repository.UserModel, emailId string, token string, managerAuth func(resource, token, object string) bool) (*userBean.UserInfo, error) { + updateUserInfo, err := impl.GetByIdWithoutGroupClaims(dbUser.Id) if err != nil && err != pg.ErrNoRows { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } if dbUser.Active == false { - updateUserInfo = &bean.UserInfo{Id: dbUser.Id} + updateUserInfo = &userBean.UserInfo{Id: dbUser.Id} userInfo.Id = dbUser.Id updateUserInfo.SuperAdmin = userInfo.SuperAdmin } updateUserInfo.RoleFilters = impl.mergeRoleFilter(updateUserInfo.RoleFilters, userInfo.RoleFilters) updateUserInfo.Groups = impl.mergeGroups(updateUserInfo.Groups, userInfo.Groups) updateUserInfo.UserRoleGroup = impl.mergeUserRoleGroup(updateUserInfo.UserRoleGroup, userInfo.UserRoleGroup) + impl.mergeAccessRoleFiltersAndUserGroups(updateUserInfo, userInfo) updateUserInfo.UserId = userInfo.UserId - updateUserInfo.EmailId = emailId // override case sensitivity + updateUserInfo.EmailId = emailId // override case sensitivity + impl.logger.Debugw("update user called through create user flow", "user", updateUserInfo) updateUserInfo, err = impl.UpdateUser(updateUserInfo, token, nil, managerAuth) //rbac already checked in create request handled if err != nil { impl.logger.Errorw("error while update user", "error", err) @@ -349,7 +368,7 @@ func (impl *UserServiceImpl) updateUserIfExists(userInfo *bean.UserInfo, dbUser return userInfo, nil } -func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emailId string) (*bean.UserInfo, error) { +func (impl *UserServiceImpl) createUserIfNotExists(userInfo *userBean.UserInfo, emailId string) (*userBean.UserInfo, error) { // if not found, create new user dbConnection := impl.userRepository.GetConnection() tx, err := dbConnection.Begin() @@ -359,127 +378,258 @@ func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emai // Rollback tx on error. defer tx.Rollback() - _, err = impl.validateUserRequest(userInfo) + err = impl.validateUserRequest(userInfo) if err != nil { - err = &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"} + impl.logger.Errorw("error in createUserIfNotExists", "request", userInfo, "err", err) return nil, err } //create new user in our db on d basis of info got from google api or hex. assign a basic role - model := &repository.UserModel{ - EmailId: emailId, - AccessToken: userInfo.AccessToken, - UserType: userInfo.UserType, - } + model := adapter2.GetUserModelBasicAdapter(emailId, userInfo.AccessToken, userInfo.UserType) model.Active = true - model.CreatedBy = userInfo.UserId - model.UpdatedBy = userInfo.UserId - model.CreatedOn = time.Now() - model.UpdatedOn = time.Now() + model.AuditLog = sql.NewDefaultAuditLog(userInfo.UserId) + err = impl.setTimeoutWindowConfigIdInUserModel(tx, userInfo, model) + if err != nil { + impl.logger.Errorw("error encountered in createUserIfNotExists", "err", err) + return nil, err + } model, err = impl.userRepository.CreateUser(model, tx) if err != nil { impl.logger.Errorw("error in creating new user", "error", err) - err = &util.ApiError{ - Code: constants.UserCreateDBFailed, - InternalMessage: "failed to create new user in db", - UserMessage: fmt.Sprintf("requested by %d", userInfo.UserId), - } + err = util.GetApiErrorAdapter(http.StatusInternalServerError, constants.UserCreateDBFailed, "failed to create new user in db", fmt.Sprintf("requested by %d, error: %v", userInfo.UserId, err)) + return nil, err + } + err = impl.assignUserGroups(tx, userInfo, model) + if err != nil { + impl.logger.Errorw("error encountered in createUserIfNotExists", "err", err) return nil, err } - userInfo.Id = model.Id - //loading policy for safety - casbin2.LoadPolicy() - //Starts Role and Mapping - capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(userInfo.RoleFilters) - //var policies []casbin2.Policy - var policies = make([]casbin2.Policy, 0, capacity) - if userInfo.SuperAdmin == false { - for index, roleFilter := range userInfo.RoleFilters { - impl.logger.Infow("Creating Or updating User Roles for RoleFilter ") - entity := roleFilter.Entity - policiesToBeAdded, _, err := impl.CreateOrUpdateUserRolesForAllTypes(roleFilter, userInfo.UserId, model, nil, tx, entity, mapping[index]) + userInfo.Id = model.Id + userInfo.SetEntityAudit(model.AuditLog) + // check for global authorisationConfig and perform operations. + operationDone, err := impl.checkAndPerformOperationsForGroupClaims(tx, userInfo) + if err != nil { + impl.logger.Errorw("error encountered in createUserIfNotExists", "err", err) + return nil, err + } + if !operationDone { + //loading policy for safety + casbin2.LoadPolicy() + //Starts Role and Mapping + capacity, _ := impl.userCommonService.GetCapacityForRoleFilter(userInfo.RoleFilters) + //var policies []casbin2.Policy + var policies = make([]bean4.Policy, 0, capacity) + if userInfo.SuperAdmin { + policiesToBeAdded, err := impl.CreateAndAddPoliciesForSuperAdmin(tx, userInfo.UserId, model.EmailId, model.Id) if err != nil { - impl.logger.Errorw("error in creating user roles for Alltypes", "err", err) + impl.logger.Errorw("error in createUserIfNotExists", "userId", userInfo.UserId, "err", err) return nil, err } policies = append(policies, policiesToBeAdded...) - } - - // START GROUP POLICY - for _, item := range userInfo.UserRoleGroup { - userGroup, err := impl.roleGroupRepository.GetRoleGroupByName(item.RoleGroup.Name) + } else if userInfo.SuperAdmin == false { + policiesToBeAdded, err := impl.CreateAndAddPoliciesForNonSuperAdmin(tx, userInfo, emailId, model) if err != nil { + impl.logger.Errorw("error encountered in createUserIfNotExists", "err", err) return nil, err } - policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(userGroup.CasbinName)}) - // below is old code where we used to re check group access, but not needed now as we have moved group rbac to restHandler - - //hasAccessToGroup, hasSuperAdminPermission := impl.checkGroupAuth(userGroup.CasbinName, token, managerAuth, isActionPerformingUserSuperAdmin) - //if hasAccessToGroup { - //policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(userGroup.CasbinName)}) - //} else { - // restrictedGroup := adapter.CreateRestrictedGroup(item.RoleGroup.Name, hasSuperAdminPermission) - // restrictedGroups = append(restrictedGroups, restrictedGroup) - //} + policies = append(policies, policiesToBeAdded...) + } + impl.logger.Infow("Checking the length of policies to be added and Adding in casbin ") + if len(policies) > 0 { + impl.logger.Infow("Adding policies in casbin") + pRes := casbin2.AddPolicy(policies) + println(pRes) } - // END GROUP POLICY - } else if userInfo.SuperAdmin == true { - flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, userInfo.UserId) - if err != nil || flag == false { + err = impl.createAuditForCreateOperation(tx, userInfo, model) + if err != nil { + impl.logger.Errorw("error in creating audit for user", "err", err, "id", model.Id) return nil, err } - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes("", "", "", "", userBean.SUPER_ADMIN, "", "", "", "", "", "", "", false, "") + + //Ends + err = tx.Commit() if err != nil { return nil, err } - if roleModel.Id > 0 { - userRoleModel := &repository.UserRoleModel{UserId: model.Id, RoleId: roleModel.Id, AuditLog: sql.AuditLog{ - CreatedBy: userInfo.UserId, - CreatedOn: time.Now(), - UpdatedBy: userInfo.UserId, - UpdatedOn: time.Now(), - }} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) - if err != nil { - return nil, err - } - policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) + //loading policy for syncing orchestrator to casbin with newly added policies + casbin2.LoadPolicy() + } + return userInfo, nil +} + +// CreateAndAddPoliciesForNonSuperAdmin : iterates over every roleFilter and adds corresponding mappings in orchestrator and return polcies to be added in casbin. +func (impl *UserServiceImpl) CreateAndAddPoliciesForNonSuperAdmin(tx *pg.Tx, userInfo *userBean.UserInfo, emailId string, model *repository.UserModel) ([]bean4.Policy, error) { + userLoggedInId := userInfo.UserId + userRoleGroup := userInfo.UserRoleGroup + finalRoleFiltersToBeConsidered := getFinalRoleFiltersToBeConsidered(userInfo) + capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(finalRoleFiltersToBeConsidered) + var policies = make([]bean4.Policy, 0, capacity) + impl.logger.Infow("Creating Or updating User Roles for RoleFilter ") + for index, roleFilter := range finalRoleFiltersToBeConsidered { + entity := roleFilter.Entity + policiesToBeAdded, _, err := impl.CreateOrUpdateUserRolesForAllTypes(tx, roleFilter, model, nil, entity, mapping[index], userLoggedInId) + if err != nil { + impl.logger.Errorw("error in CreateAndAddPoliciesForNonSuperAdmin", "err", err, "finalRoleFiltersToBeConsidered", finalRoleFiltersToBeConsidered) + return nil, err } + policies = append(policies, policiesToBeAdded...) } - impl.logger.Infow("Checking the length of policies to be added and Adding in casbin ") - if len(policies) > 0 { - impl.logger.Infow("Adding policies in casbin") - pRes := casbin2.AddPolicy(policies) - println(pRes) + + // UserRoleGroup Addition flow starts + policiesToBeAdded, err := impl.AddUserGroupPoliciesForCasbin(tx, userRoleGroup, emailId, userLoggedInId) + if err != nil { + impl.logger.Errorw("error encountered in CreateAndAddPoliciesForNonSuperAdmin", "err", err, "emailId", emailId) + return nil, err } - //Ends - err = tx.Commit() + // UserRoleGroup Addition flow starts ends + policies = append(policies, policiesToBeAdded...) + return policies, nil +} + +// AddUserGroupPoliciesForCasbin : returns user and group mapping for casbin +func (impl *UserServiceImpl) AddUserGroupPoliciesForCasbin(tx *pg.Tx, userRoleGroup []userBean.UserRoleGroup, emailId string, userLoggedInId int32) ([]bean4.Policy, error) { + var policies = make([]bean4.Policy, 0) + groupIdRoleGroupMap, err := impl.getGroupIdRoleGroupMap(userRoleGroup) if err != nil { + impl.logger.Errorw("error in AddUserGroupPoliciesForCasbin", "userGroups", userRoleGroup, "err", err) return nil, err } - //loading policy for syncing orchestrator to casbin with newly added policies - casbin2.LoadPolicy() - return userInfo, nil + for _, item := range userRoleGroup { + userGroup := groupIdRoleGroupMap[item.RoleGroup.Id] + casbinPolicy, err := impl.getCasbinPolicyForGroup(tx, emailId, userGroup.CasbinName, item, userLoggedInId) + if err != nil { + impl.logger.Errorw("error in AddUserGroupPoliciesForCasbin", "item", item, "err", err) + return nil, err + } + policies = append(policies, casbinPolicy) + } + return policies, nil } -func (impl *UserServiceImpl) CreateOrUpdateUserRolesForAllTypes(roleFilter bean.RoleFilter, userId int32, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, tx *pg.Tx, entity string, capacity int) ([]casbin2.Policy, bool, error) { - //var policiesToBeAdded []casbin2.Policy - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) +// UpdateAndAddPoliciesForNonSuperAdmin : creates corresponding mappings in orchestrator and return policies to be added, removed from casbin with flags indicating roles changes, groups changed, groups which were restricted with error if any +func (impl *UserServiceImpl) UpdateAndAddPoliciesForNonSuperAdmin(tx *pg.Tx, model *repository.UserModel, userInfo *userBean.UserInfo, token string, managerAuth func(resource string, token string, object string) bool) ([]bean4.Policy, []bean4.Policy, []*repository.RoleModel, []*repository.RoleModel, map[string]bool, error) { + finalRoleFiltersToBeConsidered := getFinalRoleFiltersToBeConsidered(userInfo) + capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(finalRoleFiltersToBeConsidered) + addedPolicies := make([]bean4.Policy, 0, capacity) + eliminatedPolicies := make([]bean4.Policy, 0, capacity) + rolesChanged := false + + //Starts Role and Mapping + userRoleModels, err := impl.userAuthRepository.GetUserRoleMappingByUserId(model.Id) + if err != nil { + impl.logger.Errorw("error in UpdateAndAddPoliciesForNonSuperAdmin", "request", userInfo, "err", err) + return nil, nil, nil, nil, nil, err + } + existingRoleIds := make(map[int]repository.UserRoleModel) + eliminatedRoleIds := make(map[int]*repository.UserRoleModel) + for i := range userRoleModels { + existingRoleIds[userRoleModels[i].RoleId] = *userRoleModels[i] + eliminatedRoleIds[userRoleModels[i].RoleId] = userRoleModels[i] + } + + //validate role filters and user role Group for conflicts and bad payload + err = impl.validateUserRequest(userInfo) + if err != nil { + impl.logger.Errorw("error in UpdateAndAddPoliciesForNonSuperAdmin", "request", userInfo, "err", err) + return nil, nil, nil, nil, nil, err + } + + // DELETE Removed Items + items, eliminatedRoles, err := impl.userCommonService.RemoveRolesAndReturnEliminatedPolicies(userInfo, existingRoleIds, eliminatedRoleIds, tx, token, managerAuth) + if err != nil { + impl.logger.Errorw("error in UpdateAndAddPoliciesForNonSuperAdmin", "request", userInfo, "err", err) + return nil, nil, nil, nil, nil, err + } + eliminatedPolicies = append(eliminatedPolicies, items...) + if len(eliminatedPolicies) > 0 { + impl.logger.Debugw("casbin policies to remove for the request", "policies: ", eliminatedPolicies, "userInfo", userInfo) + rolesChanged = true + } + + //Adding New Policies + for index, roleFilter := range finalRoleFiltersToBeConsidered { + entity := roleFilter.Entity + + policiesToBeAdded, rolesChangedFromRoleUpdate, err := impl.CreateOrUpdateUserRolesForAllTypes(tx, roleFilter, model, existingRoleIds, entity, mapping[index], userInfo.UserId) + if err != nil { + impl.logger.Errorw("error in UpdateAndAddPoliciesForNonSuperAdmin", "request", userInfo, "err", err) + return nil, nil, nil, nil, nil, err + } + addedPolicies = append(addedPolicies, policiesToBeAdded...) + rolesChanged = rolesChanged || rolesChangedFromRoleUpdate + } + + //ROLE GROUP SETUP + + policiesToBeAdded, policiesToBeEliminated, eliminatedGroupRoles, + mapOfExistingUserRoleGroupAndTwc, err := impl.createOrUpdateUserRoleGroupsPolices(userInfo.UserRoleGroup, userInfo.EmailId, tx, userInfo.UserId, userInfo.Id) + if err != nil { + impl.logger.Errorw("error in UpdateAndAddPoliciesForNonSuperAdmin", "request", userInfo, "err", err) + return nil, nil, nil, nil, nil, err + } + addedPolicies = append(addedPolicies, policiesToBeAdded...) + eliminatedPolicies = append(eliminatedPolicies, policiesToBeEliminated...) + // END GROUP POLICY + + return addedPolicies, eliminatedPolicies, eliminatedRoles, eliminatedGroupRoles, mapOfExistingUserRoleGroupAndTwc, nil +} + +// CreateAndAddPoliciesForSuperAdmin : checks if super Admin roles else creates and creates mapping in orchestrator , returns casbin polices +func (impl *UserServiceImpl) CreateAndAddPoliciesForSuperAdmin(tx *pg.Tx, userLoggedInId int32, emailId string, userModelId int32) ([]bean4.Policy, error) { + policies := make([]bean4.Policy, 0) + flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, userLoggedInId) + if err != nil || flag == false { + return nil, err + } + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(adapter.BuildSuperAdminRoleFieldsDto()) + if err != nil { + return nil, err + } + if roleModel.Id > 0 { + userRoleModel := &repository.UserRoleModel{UserId: userModelId, RoleId: roleModel.Id, AuditLog: sql.NewDefaultAuditLog(userLoggedInId)} + userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if err != nil { + return nil, err + } + policies = append(policies, bean4.Policy{Type: "g", Sub: bean4.Subject(emailId), Obj: bean4.Object(roleModel.Role)}) + } + return policies, nil +} + +func (impl *UserServiceImpl) getGroupIdRoleGroupMap(userRoleGroups []userBean.UserRoleGroup) (map[int32]*repository.RoleGroup, error) { + groupIdRoleGroupMap := make(map[int32]*repository.RoleGroup) + if len(userRoleGroups) > 0 { + ids := make([]int32, 0, len(userRoleGroups)) + for _, userGroup := range userRoleGroups { + ids = append(ids, userGroup.RoleGroup.Id) + } + var err error + groupIdRoleGroupMap, err = impl.roleGroupService.GetGroupIdVsRoleGroupMapForIds(ids) + if err != nil { + impl.logger.Errorw("error in getGroupIdRoleGroupMap", "userRoleGroups", userRoleGroups, "err", err) + return nil, err + } + } + return groupIdRoleGroupMap, nil +} + +func (impl *UserServiceImpl) CreateOrUpdateUserRolesForAllTypes(tx *pg.Tx, roleFilter userBean.RoleFilter, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, entity string, capacity int, userId int32) ([]bean4.Policy, bool, error) { + var policiesToBeAdded = make([]bean4.Policy, 0, capacity) var err error rolesChanged := false if entity == userBean.CLUSTER_ENTITIY { - policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForClusterEntity(roleFilter, userId, model, existingRoles, tx, entity, capacity) + policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForClusterEntity(tx, roleFilter, model, existingRoles, entity, capacity, userId) if err != nil { return nil, false, err } } else if entity == userBean.EntityJobs { - policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForJobsEntity(roleFilter, userId, model, existingRoles, tx, entity, capacity) + policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForJobsEntity(tx, roleFilter, model, existingRoles, entity, capacity, userId) if err != nil { return nil, false, err } } else { - policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForOtherEntity(roleFilter, userId, model, existingRoles, tx, entity, capacity) + policiesToBeAdded, rolesChanged, err = impl.createOrUpdateUserRolesForOtherEntity(tx, roleFilter, model, existingRoles, entity, capacity, userId) if err != nil { return nil, false, err } @@ -487,64 +637,71 @@ func (impl *UserServiceImpl) CreateOrUpdateUserRolesForAllTypes(roleFilter bean. return policiesToBeAdded, rolesChanged, nil } -func (impl *UserServiceImpl) createOrUpdateUserRolesForClusterEntity(roleFilter bean.RoleFilter, userId int32, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, tx *pg.Tx, entity string, capacity int) ([]casbin2.Policy, bool, error) { +func (impl *UserServiceImpl) createOrUpdateUserRolesForClusterEntity(tx *pg.Tx, roleFilter userBean.RoleFilter, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, entity string, capacity int, userId int32) ([]bean4.Policy, bool, error) { - //var policiesToBeAdded []casbin2.Policy rolesChanged := false namespaces := strings.Split(roleFilter.Namespace, ",") groups := strings.Split(roleFilter.Group, ",") kinds := strings.Split(roleFilter.Kind, ",") resources := strings.Split(roleFilter.Resource, ",") - //capacity := len(namespaces) * len(groups) * len(kinds) * len(resources) * 2 actionType := roleFilter.Action + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") accessType := roleFilter.AccessType - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) + var policiesToBeAdded = make([]bean4.Policy, 0, capacity) + timeoutWindowConfigDto, err := impl.getTimeoutWindowConfig(tx, roleFilter, userId) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForClusterEntity", "roleFilter", roleFilter, "err", err) + return policiesToBeAdded, rolesChanged, err + } + for _, namespace := range namespaces { for _, group := range groups { for _, kind := range kinds { for _, resource := range resources { - impl.logger.Infow("Getting Role by filter for cluster") - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") - if err != nil { - return policiesToBeAdded, rolesChanged, err - } - if roleModel.Id == 0 { - impl.logger.Infow("Creating Polices for cluster", resource, kind, namespace, group) - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes("", "", "", entity, roleFilter.Cluster, namespace, group, kind, resource, actionType, accessType, "", userId) - if err != nil || flag == false { - return policiesToBeAdded, rolesChanged, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - impl.logger.Infow("getting role again for cluster") - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, "", "", "", "", accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, false, "") + for _, subaction := range subActions { + impl.logger.Infow("Getting Role by filter for cluster") + clusterRoleFieldDto := adapter.BuildClusterRoleFieldsDto(entity, accessType, roleFilter.Cluster, namespace, group, kind, resource, actionType, subaction) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(clusterRoleFieldDto) if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForClusterEntity", "err", err) return policiesToBeAdded, rolesChanged, err } if roleModel.Id == 0 { - continue - } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which are removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) - } else { - if roleModel.Id > 0 { - rolesChanged = true - userRoleModel := &repository.UserRoleModel{ - UserId: model.Id, - RoleId: roleModel.Id, - AuditLog: sql.AuditLog{ - CreatedBy: userId, - CreatedOn: time.Now(), - UpdatedBy: userId, - UpdatedOn: time.Now(), - }} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + impl.logger.Infow("Creating Polices for cluster", "resource", resource, "kind", kind, "namespace", namespace, "group", group) + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(clusterRoleFieldDto, userId) + if err != nil || flag == false { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForClusterEntity", "err", err) + return policiesToBeAdded, rolesChanged, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + impl.logger.Infow("getting role again for cluster") + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(clusterRoleFieldDto) if err != nil { - return nil, rolesChanged, err + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForClusterEntity", "err", err) + return policiesToBeAdded, rolesChanged, err + } + if roleModel.Id == 0 { + continue + } + } + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which are removed + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) + } else { + if roleModel.Id > 0 { + rolesChanged = true + userRoleModel := adapter2.GetUserRoleModelAdapter(model.Id, userId, roleModel.Id, timeoutWindowConfigDto) + userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if err != nil { + impl.logger.Errorw("error in createOrUpdateUserRolesForClusterEntity", "userId", model.Id, "roleModelId", roleModel.Id, "err", err) + return nil, rolesChanged, err + } + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) } } } @@ -554,46 +711,18 @@ func (impl *UserServiceImpl) createOrUpdateUserRolesForClusterEntity(roleFilter return policiesToBeAdded, rolesChanged, nil } -func (impl *UserServiceImpl) mergeRoleFilter(oldR []bean.RoleFilter, newR []bean.RoleFilter) []bean.RoleFilter { - var roleFilters []bean.RoleFilter +func (impl *UserServiceImpl) mergeRoleFilter(oldR []userBean.RoleFilter, newR []userBean.RoleFilter) []userBean.RoleFilter { + var roleFilters []userBean.RoleFilter keysMap := make(map[string]bool) for _, role := range oldR { - roleFilters = append(roleFilters, bean.RoleFilter{ - Entity: role.Entity, - Team: role.Team, - Environment: role.Environment, - EntityName: role.EntityName, - Action: role.Action, - AccessType: role.AccessType, - Cluster: role.Cluster, - Namespace: role.Namespace, - Group: role.Group, - Kind: role.Kind, - 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) + roleFilters = append(roleFilters, adapter.BuildRoleFilterFromRoleF(role)) + key := 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 := getUniqueKeyForRoleFilter(role) if _, ok := keysMap[key]; !ok { - roleFilters = append(roleFilters, bean.RoleFilter{ - Entity: role.Entity, - Team: role.Team, - Environment: role.Environment, - EntityName: role.EntityName, - Action: role.Action, - AccessType: role.AccessType, - Cluster: role.Cluster, - Namespace: role.Namespace, - Group: role.Group, - Kind: role.Kind, - Resource: role.Resource, - Workflow: role.Workflow, - }) + roleFilters = append(roleFilters, adapter.BuildRoleFilterFromRoleF(role)) } } return roleFilters @@ -617,16 +746,16 @@ func (impl *UserServiceImpl) mergeGroups(oldGroups []string, newGroups []string) } // mergeUserRoleGroup : patches the existing userRoleGroups and new userRoleGroups with unique key name-status-expression, -func (impl *UserServiceImpl) mergeUserRoleGroup(oldUserRoleGroups []bean.UserRoleGroup, newUserRoleGroups []bean.UserRoleGroup) []bean.UserRoleGroup { - finalUserRoleGroups := make([]bean.UserRoleGroup, 0) +func (impl *UserServiceImpl) mergeUserRoleGroup(oldUserRoleGroups []userBean.UserRoleGroup, newUserRoleGroups []userBean.UserRoleGroup) []userBean.UserRoleGroup { + finalUserRoleGroups := make([]userBean.UserRoleGroup, 0) keyMap := make(map[string]bool) for _, userRoleGroup := range oldUserRoleGroups { - key := fmt.Sprintf("%s", userRoleGroup.RoleGroup.Name) + key := getUniqueKeyForUserRoleGroup(userRoleGroup) finalUserRoleGroups = append(finalUserRoleGroups, userRoleGroup) keyMap[key] = true } for _, userRoleGroup := range newUserRoleGroups { - key := fmt.Sprintf("%s", userRoleGroup.RoleGroup.Name) + key := getUniqueKeyForUserRoleGroup(userRoleGroup) if _, ok := keyMap[key]; !ok { finalUserRoleGroups = append(finalUserRoleGroups, userRoleGroup) } @@ -634,8 +763,8 @@ func (impl *UserServiceImpl) mergeUserRoleGroup(oldUserRoleGroups []bean.UserRol return finalUserRoleGroups } -func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, checkRBACForUserUpdate func(token string, userInfo *bean.UserInfo, - isUserAlreadySuperAdmin bool, eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, error) { +func (impl *UserServiceImpl) UpdateUser(userInfo *userBean.UserInfo, token string, checkRBACForUserUpdate func(token string, userInfo *userBean.UserInfo, + isUserAlreadySuperAdmin bool, eliminatedRoleFilters, eliminatedGroupRoles []*repository.RoleModel, mapOfExistingUserRoleGroup map[string]bool) (isAuthorised bool, err error), managerAuth func(resource, token string, object string) bool) (*userBean.UserInfo, error) { //checking if request for same user is being processed isLocked := impl.getUserReqLockStateById(userInfo.Id) if isLocked { @@ -659,11 +788,6 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c } }() } - //validating if action user is not admin and trying to update user who has super admin polices, return 403 - isUserSuperAdmin, err := impl.IsSuperAdmin(int(userInfo.Id)) - if err != nil { - return nil, err - } dbConnection := impl.userRepository.GetConnection() tx, err := dbConnection.Begin() if err != nil { @@ -678,133 +802,54 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c return nil, err } - var eliminatedPolicies []casbin2.Policy - capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(userInfo.RoleFilters) - var addedPolicies = make([]casbin2.Policy, 0, capacity) + userGroupsUpdated, err := impl.updateUserGroupForUser(tx, userInfo, model) + if err != nil { + impl.logger.Errorw("error encountered in UpdateUser", "err", err) + return nil, err + } + + timeoutWindowConfigId, err := impl.getTimeoutWindowID(tx, userInfo) + if err != nil { + impl.logger.Errorw("error in UpdateUser ", "userInfo", userInfo, "err", err) + return nil, err + } + isUserActive := model.Active + operationCompleted, isUserSuperAdminOrManageAllAccess, err := impl.checkValidationAndPerformOperationsForUpdate(token, tx, model, userInfo, userGroupsUpdated, timeoutWindowConfigId) + if err != nil { + impl.logger.Errorw("error in UpdateUser", "userId", userInfo.UserId, "err", err) + return nil, err + } + if operationCompleted { + return userInfo, nil + } + var eliminatedPolicies = make([]bean4.Policy, 0) + var addedPolicies = make([]bean4.Policy, 0) //loading policy for safety casbin2.LoadPolicy() var eliminatedRoles, eliminatedGroupRoles []*repository.RoleModel - mapOfExistingUserRoleGroup := make(map[string]bool) - if userInfo.SuperAdmin == false { - //Starts Role and Mapping - userRoleModels, err := impl.userAuthRepository.GetUserRoleMappingByUserId(model.Id) - if err != nil { - return nil, err - } - existingRoleIds := make(map[int]repository.UserRoleModel) - eliminatedRoleIds := make(map[int]*repository.UserRoleModel) - for i := range userRoleModels { - existingRoleIds[userRoleModels[i].RoleId] = *userRoleModels[i] - eliminatedRoleIds[userRoleModels[i].RoleId] = userRoleModels[i] - } - - //validate role filters - _, err = impl.validateUserRequest(userInfo) - if err != nil { - err = &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"} - return nil, err - } - - // DELETE Removed Items - var items []casbin2.Policy - items, eliminatedRoles, err = impl.userCommonService.RemoveRolesAndReturnEliminatedPolicies(userInfo, existingRoleIds, eliminatedRoleIds, tx, token, managerAuth) + mapOfExistingUserRoleGroupAndTwc := make(map[string]bool) + if userInfo.SuperAdmin { + policiesToBeAdded, err := impl.CreateAndAddPoliciesForSuperAdmin(tx, userInfo.UserId, model.EmailId, model.Id) if err != nil { + impl.logger.Errorw("error in UpdateUser", "userId", userInfo.UserId, "err", err) return nil, err } - eliminatedPolicies = append(eliminatedPolicies, items...) - - //Adding New Policies - for index, roleFilter := range userInfo.RoleFilters { - entity := roleFilter.Entity - policiesToBeAdded, _, err := impl.CreateOrUpdateUserRolesForAllTypes(roleFilter, userInfo.UserId, model, existingRoleIds, tx, entity, mapping[index]) - if err != nil { - impl.logger.Errorw("error in creating user roles for All Types", "err", err) - return nil, err - } - addedPolicies = append(addedPolicies, policiesToBeAdded...) - } - - //ROLE GROUP SETUP - newGroupMap := make(map[string]string) - oldGroupMap := make(map[string]string) - userCasbinRoles, err := impl.CheckUserRoles(userInfo.Id) - if err != nil { - return nil, err - } - for _, oldItem := range userCasbinRoles { - oldGroupMap[oldItem] = oldItem - mapOfExistingUserRoleGroup[oldItem] = true - } - // START GROUP POLICY - for _, item := range userInfo.UserRoleGroup { - userGroup, err := impl.roleGroupRepository.GetRoleGroupByName(item.RoleGroup.Name) - if err != nil { - return nil, err - } - newGroupMap[userGroup.CasbinName] = userGroup.CasbinName - if _, ok := oldGroupMap[userGroup.CasbinName]; !ok { - addedPolicies = append(addedPolicies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(userGroup.CasbinName)}) - // //check permission for new group which is going to add - //hasAccessToGroup, hasSuperAdminPermission := impl.checkGroupAuth(userGroup.CasbinName, token, managerAuth, isActionPerformingUserSuperAdmin) - //if hasAccessToGroup { - // groupsModified = true - // addedPolicies = append(addedPolicies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(userGroup.CasbinName)}) - //} else { - // restrictedGroup := adapter.CreateRestrictedGroup(item.RoleGroup.Name, hasSuperAdminPermission) - // restrictedGroups = append(restrictedGroups, restrictedGroup) - //} - } - } - eliminatedGroupCasbinNames := make([]string, 0, len(newGroupMap)) - for _, item := range userCasbinRoles { - if _, ok := newGroupMap[item]; !ok { - if item != bean.SUPERADMIN { - //check permission for group which is going to eliminate - if strings.HasPrefix(item, "group:") { - eliminatedPolicies = append(eliminatedPolicies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(item)}) - eliminatedGroupCasbinNames = append(eliminatedGroupCasbinNames, item) - //hasAccessToGroup, hasSuperAdminPermission := impl.checkGroupAuth(item, token, managerAuth, isActionPerformingUserSuperAdmin) - //if hasAccessToGroup { - // if strings.HasPrefix(item, "group:") { - // groupsModified = true - // } - // eliminatedPolicies = append(eliminatedPolicies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(userInfo.EmailId), Obj: casbin2.Object(item)}) - //} else { - // restrictedGroup := adapter.CreateRestrictedGroup(item, hasSuperAdminPermission) - // restrictedGroups = append(restrictedGroups, restrictedGroup) - //} - } - } - } - } // END GROUP POLICY - if len(eliminatedGroupCasbinNames) > 0 { - eliminatedGroupRoles, err = impl.roleGroupRepository.GetRolesByGroupCasbinNames(eliminatedGroupCasbinNames) - if err != nil { - impl.logger.Errorw("error, GetRolesByGroupCasbinNames", "err", err, "eliminatedGroupCasbinNames", eliminatedGroupCasbinNames) - return nil, err - } - } - } else if userInfo.SuperAdmin == true { - flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, userInfo.UserId) - if err != nil || flag == false { - return nil, err - } - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes("", "", "", "", userBean.SUPER_ADMIN, "", "", "", "", "", "", "", false, "") + addedPolicies = append(addedPolicies, policiesToBeAdded...) + } else { + var policiesToBeAdded, policiesToBeEliminated []bean4.Policy + policiesToBeAdded, policiesToBeEliminated, eliminatedRoles, + eliminatedGroupRoles, mapOfExistingUserRoleGroupAndTwc, + err = impl.UpdateAndAddPoliciesForNonSuperAdmin(tx, model, userInfo, token, managerAuth) if err != nil { + impl.logger.Errorw("error encountered in UpdateUser", "request", userInfo, "err", err) return nil, err } - if roleModel.Id > 0 { - userRoleModel := &repository.UserRoleModel{UserId: model.Id, RoleId: roleModel.Id} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) - if err != nil { - return nil, err - } - addedPolicies = append(addedPolicies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) - } + addedPolicies = append(addedPolicies, policiesToBeAdded...) + eliminatedPolicies = append(eliminatedPolicies, policiesToBeEliminated...) } if checkRBACForUserUpdate != nil { - isAuthorised, err := checkRBACForUserUpdate(token, userInfo, isUserSuperAdmin, eliminatedRoles, eliminatedGroupRoles, mapOfExistingUserRoleGroup) + isAuthorised, err := checkRBACForUserUpdate(token, userInfo, isUserSuperAdminOrManageAllAccess, eliminatedRoles, eliminatedGroupRoles, mapOfExistingUserRoleGroupAndTwc) if err != nil { impl.logger.Errorw("error in checking RBAC for user update", "err", err, "userInfo", userInfo) return nil, err @@ -820,10 +865,12 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c //updating in casbin if len(eliminatedPolicies) > 0 { + impl.logger.Debugw("casbin policies being eliminated", "policies: ", eliminatedPolicies, "userInfo", userInfo) pRes := casbin2.RemovePolicy(eliminatedPolicies) println(pRes) } if len(addedPolicies) > 0 { + impl.logger.Debugw("casbin policies being added", "policies: ", addedPolicies) pRes := casbin2.AddPolicy(addedPolicies) println(pRes) } @@ -832,12 +879,26 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c model.EmailId = userInfo.EmailId // override case sensitivity model.UpdatedOn = time.Now() model.UpdatedBy = userInfo.UserId + if !isUserActive { + // resetting created on and updated on for this email id as this was previously deleted and again created so + // new identity will be present + model.CreatedOn = time.Now() + model.CreatedBy = userInfo.UserId + } model.Active = true + setTwcId(model, timeoutWindowConfigId) model, err = impl.userRepository.UpdateUser(model, tx) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } + + err = impl.saveAuditBasedOnActiveOrInactiveUser(tx, isUserActive, model, userInfo) + if err != nil { + impl.logger.Errorw("error in creating audit for user", "err", err, "id", model.Id) + return nil, err + } + err = tx.Commit() if err != nil { return nil, err @@ -847,7 +908,7 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, c return userInfo, nil } -func (impl *UserServiceImpl) GetById(id int32) (*bean.UserInfo, error) { +func (impl *UserServiceImpl) GetByIdWithoutGroupClaims(id int32) (*userBean.UserInfo, error) { model, err := impl.userRepository.GetById(id) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) @@ -863,7 +924,7 @@ func (impl *UserServiceImpl) GetById(id int32) (*bean.UserInfo, error) { } } } - response := &bean.UserInfo{ + response := &userBean.UserInfo{ Id: model.Id, EmailId: model.EmailId, RoleFilters: roleFilters, @@ -875,14 +936,14 @@ func (impl *UserServiceImpl) GetById(id int32) (*bean.UserInfo, error) { return response, nil } -func (impl *UserServiceImpl) getUserMetadata(model *repository.UserModel) (bool, []bean.RoleFilter, []string, []bean.UserRoleGroup) { +func (impl *UserServiceImpl) getUserMetadata(model *repository.UserModel) (bool, []userBean.RoleFilter, []string, []userBean.UserRoleGroup) { roles, err := impl.userAuthRepository.GetRolesByUserId(model.Id) if err != nil { impl.logger.Debugw("No Roles Found for user", "id", model.Id) } isSuperAdmin := userHelper.CheckIfSuperAdminFromRoles(roles) - var roleFilters []bean.RoleFilter + var roleFilters []userBean.RoleFilter // merging considering base as env first roleFilters = impl.userCommonService.BuildRoleFiltersAfterMerging(ConvertRolesToEntityProcessors(roles), userBean.EnvironmentBasedKey) // merging role filters based on application now, first took env as base merged, now application as base , merged @@ -894,7 +955,7 @@ func (impl *UserServiceImpl) getUserMetadata(model *repository.UserModel) (bool, } var filterGroups []string - var userRoleGroups []bean.UserRoleGroup + var userRoleGroups []userBean.UserRoleGroup for _, item := range groups { if strings.Contains(item, "group:") { filterGroups = append(filterGroups, item) @@ -908,7 +969,7 @@ func (impl *UserServiceImpl) getUserMetadata(model *repository.UserModel) (bool, } filterGroups = nil for _, item := range filterGroupsModels { - userRoleGroups = append(userRoleGroups, bean.UserRoleGroup{RoleGroup: &bean.RoleGroup{Name: item.Name, Id: item.Id, Description: item.Description}}) + userRoleGroups = append(userRoleGroups, userBean.UserRoleGroup{RoleGroup: &userBean.RoleGroup{Name: item.Name, Id: item.Id, Description: item.Description}}) filterGroups = append(filterGroups, item.Name) } } else { @@ -919,40 +980,45 @@ func (impl *UserServiceImpl) getUserMetadata(model *repository.UserModel) (bool, filterGroups = make([]string, 0) } if len(roleFilters) == 0 { - roleFilters = make([]bean.RoleFilter, 0) + roleFilters = make([]userBean.RoleFilter, 0) } if len(userRoleGroups) == 0 { - userRoleGroups = make([]bean.UserRoleGroup, 0) + userRoleGroups = make([]userBean.UserRoleGroup, 0) } return isSuperAdmin, roleFilters, filterGroups, userRoleGroups } // GetAll excluding API token user -func (impl *UserServiceImpl) GetAll() ([]bean.UserInfo, error) { +func (impl *UserServiceImpl) GetAll() ([]userBean.UserInfo, error) { model, err := impl.userRepository.GetAllExcludingApiTokenUser() if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - var response []bean.UserInfo + userIdVsUserGroupMapDto, err := impl.getUserGroupMapFromModels(model) + if err != nil { + impl.logger.Errorw("error while fetching user group from db", "error", err) + return nil, err + } + + var response []userBean.UserInfo for _, m := range model { - response = append(response, bean.UserInfo{ - Id: m.Id, - EmailId: m.EmailId, - RoleFilters: make([]bean.RoleFilter, 0), - Groups: make([]string, 0), - }) + response = append(response, adapter.BuildUserInfo(m.Id, m.EmailId, userIdVsUserGroupMapDto)) } if len(response) == 0 { - response = make([]bean.UserInfo, 0) + response = make([]userBean.UserInfo, 0) } return response, nil } // GetAllWithFilters takes filter request gives UserListingResponse as output with some operations like filter, sorting, searching,pagination support inbuilt -func (impl *UserServiceImpl) GetAllWithFilters(request *bean.ListingRequest) (*bean.UserListingResponse, error) { +func (impl *UserServiceImpl) GetAllWithFilters(request *userBean.ListingRequest) (*userBean.UserListingResponse, error) { // default values will be used if not provided impl.userCommonService.SetDefaultValuesIfNotPresent(request, false) + // setting filter status type + setStatusFilterType(request) + // Recording time here for overall consistency + setCurrentTimeInUserInfo(request) if request.ShowAll { response, err := impl.getAllDetailedUsers(request) if err != nil { @@ -991,49 +1057,50 @@ func (impl *UserServiceImpl) GetAllWithFilters(request *bean.ListingRequest) (*b } -func (impl *UserServiceImpl) getAllDetailedUsersAdapter(detailedUsers []bean.UserInfo) *bean.UserListingResponse { - listingResponse := &bean.UserListingResponse{ +func (impl *UserServiceImpl) getAllDetailedUsersAdapter(detailedUsers []userBean.UserInfo) *userBean.UserListingResponse { + listingResponse := &userBean.UserListingResponse{ Users: detailedUsers, TotalCount: len(detailedUsers), } return listingResponse } -func (impl *UserServiceImpl) getUserResponse(model []repository.UserModel, totalCount int) (*bean.UserListingResponse, error) { - var response []bean.UserInfo +func (impl *UserServiceImpl) getUserResponse(model []repository.UserModel, totalCount int) (*userBean.UserListingResponse, error) { + userIdVsUserGroupMapDto, err := impl.getUserGroupMapFromModels(model) + if err != nil { + impl.logger.Errorw("error while fetching user group from db", "error", err) + return nil, err + } + + var response []userBean.UserInfo for _, m := range model { - lastLoginTime := adapter.GetLastLoginTime(m) - response = append(response, bean.UserInfo{ - Id: m.Id, - EmailId: m.EmailId, - RoleFilters: make([]bean.RoleFilter, 0), - Groups: make([]string, 0), - LastLoginTime: lastLoginTime, - UserRoleGroup: make([]bean.UserRoleGroup, 0), - }) + lastLoginTime := adapter2.GetLastLoginTime(m) + userinfoRes := adapter.BuildUserInfo(m.Id, m.EmailId, userIdVsUserGroupMapDto) + userinfoRes.LastLoginTime = lastLoginTime + response = append(response, userinfoRes) } if len(response) == 0 { - response = make([]bean.UserInfo, 0) + response = make([]userBean.UserInfo, 0) } - listingResponse := &bean.UserListingResponse{ + listingResponse := &userBean.UserListingResponse{ Users: response, TotalCount: totalCount, } return listingResponse, nil } -func (impl *UserServiceImpl) getAllDetailedUsers(req *bean.ListingRequest) ([]bean.UserInfo, error) { +func (impl *UserServiceImpl) getAllDetailedUsers(req *userBean.ListingRequest) ([]userBean.UserInfo, error) { query, queryParams := helper.GetQueryForUserListingWithFilters(req) models, err := impl.userRepository.GetAllExecutingQuery(query, queryParams) if err != nil { impl.logger.Errorw("error in GetAllDetailedUsers", "err", err) return nil, err } - var response []bean.UserInfo + var response []userBean.UserInfo for _, model := range models { isSuperAdmin, roleFilters, filterGroups, userRoleGroups := impl.getUserMetadata(&model) - lastLoginTime := adapter.GetLastLoginTime(model) + lastLoginTime := adapter2.GetLastLoginTime(model) for index, roleFilter := range roleFilters { if roleFilter.Entity == "" { roleFilters[index].Entity = userBean.ENTITY_APPS @@ -1042,7 +1109,7 @@ func (impl *UserServiceImpl) getAllDetailedUsers(req *bean.ListingRequest) ([]be roleFilters[index].AccessType = userBean.DEVTRON_APP } } - response = append(response, bean.UserInfo{ + response = append(response, userBean.UserInfo{ Id: model.Id, EmailId: model.EmailId, RoleFilters: roleFilters, @@ -1053,18 +1120,18 @@ func (impl *UserServiceImpl) getAllDetailedUsers(req *bean.ListingRequest) ([]be }) } if len(response) == 0 { - response = make([]bean.UserInfo, 0) + response = make([]userBean.UserInfo, 0) } return response, nil } -func (impl *UserServiceImpl) GetAllDetailedUsers() ([]bean.UserInfo, error) { +func (impl *UserServiceImpl) GetAllDetailedUsers() ([]userBean.UserInfo, error) { models, err := impl.userRepository.GetAllExcludingApiTokenUser() if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - var response []bean.UserInfo + var response []userBean.UserInfo for _, model := range models { isSuperAdmin, roleFilters, filterGroups, _ := impl.getUserMetadata(&model) for index, roleFilter := range roleFilters { @@ -1075,7 +1142,7 @@ func (impl *UserServiceImpl) GetAllDetailedUsers() ([]bean.UserInfo, error) { roleFilters[index].AccessType = userBean.DEVTRON_APP } } - response = append(response, bean.UserInfo{ + response = append(response, userBean.UserInfo{ Id: model.Id, EmailId: model.EmailId, RoleFilters: roleFilters, @@ -1084,7 +1151,7 @@ func (impl *UserServiceImpl) GetAllDetailedUsers() ([]bean.UserInfo, error) { }) } if len(response) == 0 { - response = make([]bean.UserInfo, 0) + response = make([]userBean.UserInfo, 0) } return response, nil } @@ -1106,7 +1173,7 @@ func (impl *UserServiceImpl) UserExists(emailId string) bool { func (impl *UserServiceImpl) SaveLoginAudit(emailId, clientIp string, id int32) { if emailId != "" && id <= 0 { - user, err := impl.getUserByEmail(emailId) + user, err := impl.GetUserBasicDataByEmailId(emailId) if err != nil { impl.logger.Errorw("error in getting userInfo by emailId", "err", err, "emailId", emailId) return @@ -1127,45 +1194,6 @@ func (impl *UserServiceImpl) SaveLoginAudit(emailId, clientIp string, id int32) } } -func (impl *UserServiceImpl) getUserByEmail(emailId string) (*bean.UserInfo, error) { - model, err := impl.userRepository.FetchActiveUserByEmail(emailId) - if err != nil { - impl.logger.Errorw("error while fetching user from db", "error", err) - return nil, err - } - - roles, err := impl.userAuthRepository.GetRolesByUserId(model.Id) - if err != nil { - impl.logger.Warnw("No Roles Found for user", "id", model.Id) - } - var roleFilters []bean.RoleFilter - for _, role := range roles { - roleFilters = append(roleFilters, bean.RoleFilter{ - Entity: role.Entity, - Team: role.Team, - Environment: role.Environment, - EntityName: role.EntityName, - Action: role.Action, - Cluster: role.Cluster, - Namespace: role.Namespace, - Group: role.Group, - Kind: role.Kind, - Resource: role.Resource, - Workflow: role.Workflow, - }) - } - - response := &bean.UserInfo{ - Id: model.Id, - EmailId: model.EmailId, - UserType: model.UserType, - AccessToken: model.AccessToken, - RoleFilters: roleFilters, - } - - return response, nil -} - func (impl *UserServiceImpl) GetActiveEmailById(userId int32) (string, error) { var emailId string model, err := impl.userRepository.GetById(userId) @@ -1207,7 +1235,7 @@ func (impl *UserServiceImpl) GetLoggedInUser(r *http.Request) (int32, error) { } userId, userType, err := impl.GetUserByToken(r.Context(), token) // if user is of api-token type, then update lastUsedBy and lastUsedAt - if err == nil && userType == bean.USER_TYPE_API_TOKEN { + if err == nil && userType == userBean.USER_TYPE_API_TOKEN { go impl.saveUserAudit(r, userId) } return userId, err @@ -1220,7 +1248,7 @@ func (impl *UserServiceImpl) GetUserByToken(context context.Context, token strin if err != nil { return http.StatusUnauthorized, "", err } - userInfo, err := impl.getUserByEmail(email) + userInfo, err := impl.GetUserBasicDataByEmailId(email) if err != nil { impl.logger.Errorw("unable to fetch user from db", "error", err) err := &util.ApiError{ @@ -1233,7 +1261,7 @@ func (impl *UserServiceImpl) GetUserByToken(context context.Context, token strin // checking length of version, to ensure backward compatibility as earlier we did not // have version for api-tokens // therefore, for tokens without version we will skip the below part - if userInfo.UserType == bean.USER_TYPE_API_TOKEN && len(version) > 0 { + if userInfo.UserType == userBean.USER_TYPE_API_TOKEN && len(version) > 0 { err := impl.CheckIfTokenIsValid(email, version) if err != nil { impl.logger.Errorw("token is not valid", "error", err, "token", token) @@ -1266,10 +1294,7 @@ func (impl *UserServiceImpl) CheckIfTokenIsValid(email string, version string) e func (impl *UserServiceImpl) GetEmailFromToken(token string) (string, error) { if token == "" { impl.logger.Infow("no token provided") - err := &util.ApiError{ - Code: constants.UserNoTokenProvided, - InternalMessage: "no token provided", - } + err := adapter.GetNoTokenProvidedError() return "", err } @@ -1350,22 +1375,30 @@ func (impl *UserServiceImpl) GetEmailAndVersionFromToken(token string) (string, return util3.ConvertEmailToLowerCase(email), tokenVersion, nil } -func (impl *UserServiceImpl) GetByIds(ids []int32) ([]bean.UserInfo, error) { - var beans []bean.UserInfo +func (impl *UserServiceImpl) GetByIds(ids []int32) ([]userBean.UserInfo, error) { + var beans []userBean.UserInfo + if len(ids) == 0 { + return beans, nil + } models, err := impl.userRepository.GetByIds(ids) if err != nil && err != pg.ErrNoRows { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } + userIdVsUserGroupMapDto, err := impl.getUserGroupMapFromModels(models) + if err != nil { + impl.logger.Errorw("error while fetching user group from db", "error", err) + return nil, err + } if len(models) > 0 { for _, item := range models { - beans = append(beans, bean.UserInfo{Id: item.Id, EmailId: item.EmailId}) + beans = append(beans, adapter.BuildUserInfo(item.Id, item.EmailId, userIdVsUserGroupMapDto)) } } return beans, nil } -func (impl *UserServiceImpl) DeleteUser(bean *bean.UserInfo) (bool, error) { +func (impl *UserServiceImpl) DeleteUser(userInfo *userBean.UserInfo) (bool, error) { dbConnection := impl.roleGroupRepository.GetConnection() tx, err := dbConnection.Begin() @@ -1375,12 +1408,12 @@ func (impl *UserServiceImpl) DeleteUser(bean *bean.UserInfo) (bool, error) { // Rollback tx on error. defer tx.Rollback() - model, err := impl.userRepository.GetById(bean.Id) + model, err := impl.userRepository.GetById(userInfo.Id) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return false, err } - userRolesMappingIds, err := impl.userAuthRepository.GetUserRoleMappingIdsByUserId(bean.Id) + userRolesMappingIds, err := impl.userAuthRepository.GetUserRoleMappingIdsByUserId(userInfo.Id) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return false, err @@ -1393,34 +1426,29 @@ func (impl *UserServiceImpl) DeleteUser(bean *bean.UserInfo) (bool, error) { } } model.Active = false - model.UpdatedBy = bean.UserId + model.UpdatedBy = userInfo.UserId model.UpdatedOn = time.Now() + setTwcId(model, 0) model, err = impl.userRepository.UpdateUser(model, tx) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return false, err } - err = tx.Commit() + + err = impl.deleteUserCasbinPolices(model) if err != nil { - return false, err + impl.logger.Errorw("error while deleting user policies", "error", err) } - groups, err := casbin2.GetRolesForUser(model.EmailId) + err = tx.Commit() if err != nil { - impl.logger.Warnw("No Roles Found for user", "id", model.Id) - } - for _, item := range groups { - flag := casbin2.DeleteRoleForUser(model.EmailId, item) - if flag == false { - impl.logger.Warnw("unable to delete role:", "user", model.EmailId, "role", item) - } + return false, err } - return true, nil } // BulkDeleteUsers takes in BulkDeleteRequest and return success and error -func (impl *UserServiceImpl) BulkDeleteUsers(request *bean.BulkDeleteRequest) (bool, error) { +func (impl *UserServiceImpl) BulkDeleteUsers(request *userBean.BulkDeleteRequest) (bool, error) { // it handles ListingRequest if filters are applied will delete those users or will consider the given user ids. if request.ListingRequest != nil { filteredUserIds, err := impl.getUserIdsHonoringFilters(request.ListingRequest) @@ -1440,7 +1468,7 @@ func (impl *UserServiceImpl) BulkDeleteUsers(request *bean.BulkDeleteRequest) (b } // getUserIdsHonoringFilters get the filtered user ids according to the request filters and returns userIds and error(not nil) if any exception is caught. -func (impl *UserServiceImpl) getUserIdsHonoringFilters(request *bean.ListingRequest) ([]int32, error) { +func (impl *UserServiceImpl) getUserIdsHonoringFilters(request *userBean.ListingRequest) ([]int32, error) { //query to get particular models respecting filters query, queryParams := helper.GetQueryForUserListingWithFilters(request) models, err := impl.userRepository.GetAllExecutingQuery(query, queryParams) @@ -1459,7 +1487,8 @@ func (impl *UserServiceImpl) getUserIdsHonoringFilters(request *bean.ListingRequ } // deleteUsersByIds bulk delete all the users with their user role mappings in orchestrator and user-role and user-group mappings from casbin, takes in BulkDeleteRequest request and return success and error in return -func (impl *UserServiceImpl) deleteUsersByIds(request *bean.BulkDeleteRequest) error { +func (impl *UserServiceImpl) deleteUsersByIds(request *userBean.BulkDeleteRequest) error { + recordedTime := time.Now() tx, err := impl.roleGroupRepository.StartATransaction() if err != nil { impl.logger.Errorw("error in starting a transaction", "err", err) @@ -1481,7 +1510,7 @@ func (impl *UserServiceImpl) deleteUsersByIds(request *bean.BulkDeleteRequest) e return err } // updating models to inactive - err = impl.userRepository.UpdateToInactiveByIds(request.Ids, tx, request.LoggedInUserId) + err = impl.userRepository.UpdateToInactiveByIds(request.Ids, tx, request.LoggedInUserId, recordedTime) if err != nil { impl.logger.Errorw("error encountered in DeleteUsersForIds", "err", err) return err @@ -1540,31 +1569,6 @@ func (impl *UserServiceImpl) deleteMappingsFromOrchestrator(userIds []int32, tx return nil } -func (impl *UserServiceImpl) CheckUserRoles(id int32) ([]string, error) { - model, err := impl.userRepository.GetByIdIncludeDeleted(id) - if err != nil { - impl.logger.Errorw("error while fetching user from db", "error", err) - return nil, err - } - - groups, err := casbin2.GetRolesForUser(model.EmailId) - if err != nil { - impl.logger.Errorw("No Roles Found for user", "id", model.Id) - return nil, err - } - if len(groups) > 0 { - // getting unique, handling for duplicate roles - roleFromGroups, err := impl.getUniquesRolesByGroupCasbinNames(groups) - if err != nil { - impl.logger.Errorw("error in getUniquesRolesByGroupCasbinNames", "err", err) - return nil, err - } - groups = append(groups, roleFromGroups...) - } - - return groups, nil -} - func (impl *UserServiceImpl) getUniquesRolesByGroupCasbinNames(groupCasbinNames []string) ([]string, error) { rolesModels, err := impl.roleGroupRepository.GetRolesByGroupCasbinNames(groupCasbinNames) if err != nil && err != pg.ErrNoRows { @@ -1615,13 +1619,13 @@ func (impl *UserServiceImpl) SyncOrchestratorToCasbin() (bool, error) { func (impl *UserServiceImpl) IsSuperAdmin(userId int) (bool, error) { //validating if action user is not admin and trying to update user who has super admin polices, return 403 isSuperAdmin := false - userCasbinRoles, err := impl.CheckUserRoles(int32(userId)) + userCasbinRoles, err := impl.CheckUserRoles(int32(userId), "") if err != nil { return isSuperAdmin, err } //if user which going to updated is super admin, action performing user also be super admin for _, item := range userCasbinRoles { - if item == bean.SUPERADMIN { + if item == userBean.SUPERADMIN { isSuperAdmin = true break } @@ -1629,13 +1633,13 @@ func (impl *UserServiceImpl) IsSuperAdmin(userId int) (bool, error) { return isSuperAdmin, nil } -func (impl *UserServiceImpl) GetByIdIncludeDeleted(id int32) (*bean.UserInfo, error) { +func (impl *UserServiceImpl) GetByIdIncludeDeleted(id int32) (*userBean.UserInfo, error) { model, err := impl.userRepository.GetByIdIncludeDeleted(id) if err != nil { impl.logger.Errorw("error while fetching user from db", "error", err) return nil, err } - response := &bean.UserInfo{ + response := &userBean.UserInfo{ Id: model.Id, EmailId: model.EmailId, } @@ -1662,42 +1666,7 @@ func (impl *UserServiceImpl) saveUserAudit(r *http.Request, userId int32) { impl.userAuditService.Save(userAudit) } -func (impl *UserServiceImpl) checkGroupAuth(groupName string, token string, managerAuth func(resource, token string, object string) bool, isActionUserSuperAdmin bool) (bool, bool) { - //check permission for group which is going to add/eliminate - roles, err := impl.roleGroupRepository.GetRolesByGroupCasbinName(groupName) - if err != nil && err != pg.ErrNoRows { - impl.logger.Errorw("error while fetching user from db", "error", err) - return false, false - } - hasAccessToGroup := true - hasSuperAdminPermission := false - for _, role := range roles { - if role.Role == bean.SUPERADMIN && !isActionUserSuperAdmin { - hasAccessToGroup = false - hasSuperAdminPermission = true - } - if role.AccessType == userBean.APP_ACCESS_TYPE_HELM && !isActionUserSuperAdmin { - hasAccessToGroup = false - } - if len(role.Team) > 0 { - rbacObject := fmt.Sprintf("%s", role.Team) - isValidAuth := managerAuth(casbin2.ResourceUser, token, rbacObject) - if !isValidAuth { - hasAccessToGroup = false - } - } - if role.Entity == userBean.CLUSTER_ENTITIY && !isActionUserSuperAdmin { - isValidAuth := impl.userCommonService.CheckRbacForClusterEntity(role.Cluster, role.Namespace, role.Group, role.Kind, role.Resource, token, managerAuth) - if !isValidAuth { - hasAccessToGroup = false - } - } - - } - return hasAccessToGroup, hasSuperAdminPermission -} - -func (impl *UserServiceImpl) GetRoleFiltersByUserRoleGroups(userRoleGroups []bean.UserRoleGroup) ([]bean.RoleFilter, error) { +func (impl *UserServiceImpl) GetRoleFiltersByUserRoleGroups(userRoleGroups []userBean.UserRoleGroup) ([]userBean.RoleFilter, error) { groupNames := make([]string, 0) for _, userRoleGroup := range userRoleGroups { groupNames = append(groupNames, userRoleGroup.RoleGroup.Name) @@ -1710,7 +1679,7 @@ func (impl *UserServiceImpl) GetRoleFiltersByUserRoleGroups(userRoleGroups []bea impl.logger.Errorw("error in getting roles by group names", "err", err) return nil, err } - var roleFilters []bean.RoleFilter + var roleFilters []userBean.RoleFilter roleFilters = impl.userCommonService.BuildRoleFiltersAfterMerging(ConvertRolesToEntityProcessors(roles), userBean.EnvironmentBasedKey) // merging role filters based on application now, first took env as base merged, now application as base , merged @@ -1718,113 +1687,142 @@ func (impl *UserServiceImpl) GetRoleFiltersByUserRoleGroups(userRoleGroups []bea return roleFilters, nil } -func (impl *UserServiceImpl) createOrUpdateUserRolesForOtherEntity(roleFilter bean.RoleFilter, userId int32, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, tx *pg.Tx, entity string, capacity int) ([]casbin2.Policy, bool, error) { +func (impl *UserServiceImpl) createOrUpdateUserRolesForOtherEntity(tx *pg.Tx, roleFilter userBean.RoleFilter, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, entity string, capacity int, userId int32) ([]bean4.Policy, bool, error) { rolesChanged := false - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) - actionType := roleFilter.Action + var policiesToBeAdded = make([]bean4.Policy, 0, capacity) accessType := roleFilter.AccessType entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") + actions := strings.Split(roleFilter.Action, ",") + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + approver := getApproverFromRoleFilter(roleFilter) + timeoutWindowConfigDto, err := impl.getTimeoutWindowConfig(tx, roleFilter, userId) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForOtherEntity", "roleFilter", roleFilter, "err", err) + return policiesToBeAdded, rolesChanged, err + } for _, environment := range environments { for _, entityName := range entityNames { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, "") - if err != nil { - impl.logger.Errorw("error in getting role by all type", "err", err, "roleFilter", roleFilter) - return policiesToBeAdded, rolesChanged, err - } - if roleModel.Id == 0 { - impl.logger.Debugw("no role found for given filter", "filter", "roleFilter", roleFilter) - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(roleFilter.Team, entityName, environment, entity, "", "", "", "", "", actionType, accessType, "", userId) - if err != nil || flag == false { - return policiesToBeAdded, rolesChanged, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, "") - if err != nil { - return policiesToBeAdded, rolesChanged, err - } - if roleModel.Id == 0 { - continue - } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which is removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) - } else if roleModel.Id > 0 { - rolesChanged = true - userRoleModel := &repository.UserRoleModel{ - UserId: model.Id, - RoleId: roleModel.Id, - AuditLog: sql.AuditLog{ - CreatedBy: userId, - CreatedOn: time.Now(), - UpdatedBy: userId, - UpdatedOn: time.Now(), - }} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) - if err != nil { - return nil, rolesChanged, err + for _, actionType := range actions { + for _, subaction := range subActions { + otherEntityRoleFieldDto := adapter.BuildOtherRoleFieldsDto(entity, roleFilter.Team, entityName, environment, actionType, accessType, false, subaction, approver) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(otherEntityRoleFieldDto) + if err != nil { + impl.logger.Errorw("error in getting role by all type", "err", err, "roleFilter", roleFilter) + return policiesToBeAdded, rolesChanged, err + } + if roleModel.Id == 0 { + impl.logger.Debugw("no role found for given filter", "roleFilter", roleFilter) + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(otherEntityRoleFieldDto, userId) + if err != nil || flag == false { + return policiesToBeAdded, rolesChanged, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(otherEntityRoleFieldDto) + if err != nil { + return policiesToBeAdded, rolesChanged, err + } + if roleModel.Id == 0 { + continue + } + } + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which is removed + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) + } else if roleModel.Id > 0 { + rolesChanged = true + userRoleModel := adapter2.GetUserRoleModelAdapter(model.Id, userId, roleModel.Id, timeoutWindowConfigDto) + userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if err != nil { + impl.logger.Errorw("error in createOrUpdateUserRolesForOtherEntity", "userId", model.Id, "roleModelId", roleModel.Id, "err", err) + return nil, rolesChanged, err + } + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) + } } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) } } } return policiesToBeAdded, rolesChanged, nil } -func (impl *UserServiceImpl) createOrUpdateUserRolesForJobsEntity(roleFilter bean.RoleFilter, userId int32, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, tx *pg.Tx, entity string, capacity int) ([]casbin2.Policy, bool, error) { +func (impl *UserServiceImpl) createOrUpdateUserRolesForJobsEntity(tx *pg.Tx, roleFilter userBean.RoleFilter, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, entity string, capacity int, userId int32) ([]bean4.Policy, bool, error) { rolesChanged := false actionType := roleFilter.Action accessType := roleFilter.AccessType - var policiesToBeAdded = make([]casbin2.Policy, 0, capacity) + var policiesToBeAdded = make([]bean4.Policy, 0, capacity) entityNames := strings.Split(roleFilter.EntityName, ",") environments := strings.Split(roleFilter.Environment, ",") workflows := strings.Split(roleFilter.Workflow, ",") + subAction := getSubactionFromRoleFilter(roleFilter) + subActions := strings.Split(subAction, ",") + timeoutWindowConfigDto, err := impl.getTimeoutWindowConfig(tx, roleFilter, userId) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForJobsEntity", "roleFilter", roleFilter, "err", err) + return policiesToBeAdded, rolesChanged, err + } for _, environment := range environments { for _, entityName := range entityNames { for _, workflow := range workflows { - roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, workflow) - if err != nil { - impl.logger.Errorw("error in getting role by all type", "err", err, "roleFilter", roleFilter) - return policiesToBeAdded, rolesChanged, err - } - if roleModel.Id == 0 { - impl.logger.Debugw("no role found for given filter", "filter", "roleFilter", roleFilter) - flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(roleFilter.Team, entityName, environment, entity, "", "", "", "", "", actionType, accessType, workflow, userId) - if err != nil || flag == false { - return policiesToBeAdded, rolesChanged, err - } - policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) - roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(entity, roleFilter.Team, entityName, environment, actionType, accessType, "", "", "", "", "", actionType, false, workflow) + for _, subaction := range subActions { + jobsRoleFieldDto := adapter.BuildJobsRoleFieldsDto(entity, roleFilter.Team, entityName, environment, actionType, accessType, workflow, subaction) + roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes(jobsRoleFieldDto) if err != nil { + impl.logger.Errorw("error in getting role by all type", "err", err, "roleFilter", roleFilter) return policiesToBeAdded, rolesChanged, err } if roleModel.Id == 0 { - continue + impl.logger.Debugw("no role found for given filter", "filter", "roleFilter", roleFilter) + flag, err, policiesAdded := impl.userCommonService.CreateDefaultPoliciesForAllTypes(jobsRoleFieldDto, userId) + if err != nil || flag == false { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForJobsEntity", "err", err) + return policiesToBeAdded, rolesChanged, err + } + policiesToBeAdded = append(policiesToBeAdded, policiesAdded...) + roleModel, err = impl.userAuthRepository.GetRoleByFilterForAllTypes(jobsRoleFieldDto) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRolesForJobsEntity", "err", err) + return policiesToBeAdded, rolesChanged, err + } + if roleModel.Id == 0 { + continue + } } - } - if _, ok := existingRoles[roleModel.Id]; ok { - //Adding policies which is removed - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) - } else if roleModel.Id > 0 { - rolesChanged = true - userRoleModel := &repository.UserRoleModel{ - UserId: model.Id, - RoleId: roleModel.Id, - AuditLog: sql.AuditLog{ - CreatedBy: userId, - CreatedOn: time.Now(), - UpdatedBy: userId, - UpdatedOn: time.Now(), - }} - userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) - if err != nil { - return nil, rolesChanged, err + if _, ok := existingRoles[roleModel.Id]; ok { + //Adding policies which is removed + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) + } else if roleModel.Id > 0 { + rolesChanged = true + userRoleModel := adapter2.GetUserRoleModelAdapter(model.Id, userId, roleModel.Id, timeoutWindowConfigDto) + userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx) + if err != nil { + impl.logger.Errorw("error in createOrUpdateUserRolesForJobsEntity ", "userId", model.Id, "roleModelId", roleModel.Id, "err", err) + return nil, rolesChanged, err + } + casbinPolicy := adapter.GetCasbinGroupPolicy(model.EmailId, roleModel.Role, timeoutWindowConfigDto) + policiesToBeAdded = append(policiesToBeAdded, casbinPolicy) } - policiesToBeAdded = append(policiesToBeAdded, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)}) } } } } return policiesToBeAdded, rolesChanged, nil } + +func (impl *UserServiceImpl) GetUserBasicDataByEmailId(emailId string) (*userBean.UserInfo, error) { + model, err := impl.userRepository.FetchActiveUserByEmail(emailId) + if err != nil { + impl.logger.Errorw("error while fetching user from db", "error", err) + return nil, err + } + response := &userBean.UserInfo{ + Id: model.Id, + EmailId: model.EmailId, + UserType: model.UserType, + } + return response, nil +} diff --git a/pkg/auth/user/UserService_ent.go b/pkg/auth/user/UserService_ent.go new file mode 100644 index 0000000000..39e94f450c --- /dev/null +++ b/pkg/auth/user/UserService_ent.go @@ -0,0 +1,206 @@ +package user + +import ( + "fmt" + casbin2 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + bean4 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/adapter" + userBean "github.com/devtron-labs/devtron/pkg/auth/user/bean" + userrepo "github.com/devtron-labs/devtron/pkg/auth/user/repository" + "github.com/go-pg/pg" + "strings" +) + +func (impl *UserServiceImpl) UpdateDataForGroupClaims(dto *userBean.SelfRegisterDto) error { + return nil +} + +func (impl *UserServiceImpl) mergeAccessRoleFiltersAndUserGroups(currentUserInfo, requestUserInfo *userBean.UserInfo) { + return +} + +func (impl *UserServiceImpl) setTimeoutWindowConfigIdInUserModel(tx *pg.Tx, userInfo *userBean.UserInfo, model *userrepo.UserModel) error { + return nil +} + +func (impl *UserServiceImpl) assignUserGroups(tx *pg.Tx, userInfo *userBean.UserInfo, model *userrepo.UserModel) error { + return nil +} + +func (impl *UserServiceImpl) checkAndPerformOperationsForGroupClaims(tx *pg.Tx, userInfo *userBean.UserInfo) (bool, error) { + return false, nil +} + +func getFinalRoleFiltersToBeConsidered(userInfo *userBean.UserInfo) []userBean.RoleFilter { + return userInfo.RoleFilters +} + +func validateAccessRoleFilters(info *userBean.UserInfo) error { + return nil +} + +func (impl *UserServiceImpl) createAuditForSelfRegisterOperation(tx *pg.Tx, userResponseInfo *userBean.UserInfo) error { + return nil +} + +func (impl *UserServiceImpl) createAuditForCreateOperation(tx *pg.Tx, userResponseInfo *userBean.UserInfo, model *userrepo.UserModel) error { + return nil +} + +func (impl *UserServiceImpl) getCasbinPolicyForGroup(tx *pg.Tx, emailId, userGroupCasbinName string, userRoleGroup userBean.UserRoleGroup, userLoggedInId int32) (bean4.Policy, error) { + casbinPolicy := adapter.GetCasbinGroupPolicy(emailId, userGroupCasbinName, nil) + return casbinPolicy, nil +} + +func getUniqueKeyForRoleFilter(role userBean.RoleFilter) string { + return 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) +} + +func getUniqueKeyForUserRoleGroup(userRoleGroup userBean.UserRoleGroup) string { + return fmt.Sprintf("%s", userRoleGroup.RoleGroup.Name) +} + +func (impl *UserServiceImpl) updateUserGroupForUser(tx *pg.Tx, userInfo *userBean.UserInfo, model *userrepo.UserModel) (bool, error) { + return false, nil +} + +func (impl *UserServiceImpl) saveAuditBasedOnActiveOrInactiveUser(tx *pg.Tx, isUserActive bool, model *userrepo.UserModel, userInfo *userBean.UserInfo) error { + return nil +} + +func setStatusFilterType(request *userBean.ListingRequest) { + return +} + +func setCurrentTimeInUserInfo(request *userBean.ListingRequest) { + return +} + +func (impl *UserServiceImpl) getTimeoutWindowConfig(tx *pg.Tx, roleFilter userBean.RoleFilter, userLoggedInId int32) (*userBean.TimeoutWindowConfigDto, error) { + return nil, nil +} + +func getSubactionFromRoleFilter(roleFilter userBean.RoleFilter) string { + return "" +} + +func (impl *UserServiceImpl) CheckUserRoles(id int32, token string) ([]string, error) { + model, err := impl.userRepository.GetByIdIncludeDeleted(id) + if err != nil { + impl.logger.Errorw("error while fetching user from db", "error", err) + return nil, err + } + + groups, err := casbin2.GetRolesForUser(model.EmailId) + if err != nil { + impl.logger.Errorw("No Roles Found for user", "id", model.Id) + return nil, err + } + if len(groups) > 0 { + // getting unique, handling for duplicate roles + roleFromGroups, err := impl.getUniquesRolesByGroupCasbinNames(groups) + if err != nil { + impl.logger.Errorw("error in getUniquesRolesByGroupCasbinNames", "err", err) + return nil, err + } + groups = append(groups, roleFromGroups...) + } + + return groups, nil +} + +func (impl *UserServiceImpl) getUserGroupMapFromModels(model []userrepo.UserModel) (*userBean.UserGroupMapDto, error) { + return nil, nil +} + +func setTwcId(model *userrepo.UserModel, twcId int) { + return +} + +func (impl *UserServiceImpl) getTimeoutWindowID(tx *pg.Tx, userInfo *userBean.UserInfo) (int, error) { + return 0, nil + +} + +// createOrUpdateUserRoleGroupsPolices : gives policies which are to be added and which are to be eliminated from casbin, with support of timewindow Config changed fromm existing +func (impl *UserServiceImpl) createOrUpdateUserRoleGroupsPolices(requestUserRoleGroups []userBean.UserRoleGroup, emailId string, tx *pg.Tx, loggedInUser int32, userInfoId int32) ([]bean4.Policy, []bean4.Policy, []*userrepo.RoleModel, map[string]bool, error) { + userCasbinRoles, err := impl.CheckUserRoles(userInfoId, "") + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRoleGroupsPolices", "userRoleGroups", requestUserRoleGroups, "emailId", emailId, "err", err) + return nil, nil, nil, nil, err + } + // initialisation + + newGroupMap := make(map[string]string) + oldGroupMap := make(map[string]string) + mapOfExistingUserRoleGroup := make(map[string]bool, len(userCasbinRoles)) + addedPolicies := make([]bean4.Policy, 0) + eliminatedPolicies := make([]bean4.Policy, 0) + eliminatedGroupCasbinNames := make([]string, 0, len(newGroupMap)) + var eliminatedGroupRoles []*userrepo.RoleModel + for _, oldItem := range userCasbinRoles { + oldGroupMap[oldItem] = oldItem + mapOfExistingUserRoleGroup[oldItem] = true + } + // START GROUP POLICY + for _, item := range requestUserRoleGroups { + userGroup, err := impl.roleGroupRepository.GetRoleGroupByName(item.RoleGroup.Name) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRoleGroupsPolices", "userRoleGroups", requestUserRoleGroups, "emailId", emailId, "err", err) + return nil, nil, nil, nil, err + } + newGroupMap[userGroup.CasbinName] = userGroup.CasbinName + if _, ok := oldGroupMap[userGroup.CasbinName]; !ok { + addedPolicies = append(addedPolicies, bean4.Policy{Type: "g", Sub: bean4.Subject(emailId), Obj: bean4.Object(userGroup.CasbinName)}) + } + } + for _, item := range userCasbinRoles { + if _, ok := newGroupMap[item]; !ok { + if item != userBean.SUPERADMIN { + //check permission for group which is going to eliminate + if strings.HasPrefix(item, "group:") { + eliminatedPolicies = append(eliminatedPolicies, bean4.Policy{Type: "g", Sub: bean4.Subject(emailId), Obj: bean4.Object(item)}) + eliminatedGroupCasbinNames = append(eliminatedGroupCasbinNames, item) + } + } + } + } // END GROUP POLICY + if len(eliminatedGroupCasbinNames) > 0 { + eliminatedGroupRoles, err = impl.roleGroupRepository.GetRolesByGroupCasbinNames(eliminatedGroupCasbinNames) + if err != nil { + impl.logger.Errorw("error encountered in createOrUpdateUserRoleGroupsPolices", "userRoleGroups", requestUserRoleGroups, "emailId", emailId, "err", err) + return nil, nil, nil, nil, err + } + } + return addedPolicies, eliminatedPolicies, eliminatedGroupRoles, mapOfExistingUserRoleGroup, nil +} + +func (impl *UserServiceImpl) deleteUserCasbinPolices(model *userrepo.UserModel) error { + groups, err := casbin2.GetRolesForUser(model.EmailId) + if err != nil { + impl.logger.Warnw("No Roles Found for user", "id", model.Id) + return err + } + for _, item := range groups { + flag := casbin2.DeleteRoleForUser(model.EmailId, item) + if flag == false { + impl.logger.Warnw("unable to delete role:", "user", model.EmailId, "role", item) + } + } + return nil +} + +func getApproverFromRoleFilter(roleFilter userBean.RoleFilter) bool { + return false +} + +func (impl *UserServiceImpl) checkValidationAndPerformOperationsForUpdate(token string, tx *pg.Tx, model *userrepo.UserModel, userInfo *userBean.UserInfo, userGroupsUpdated bool, timeoutWindowConfigId int) (operationCompleted bool, isUserSuperAdmin bool, err error) { + //validating if action user is not admin and trying to update user who has super admin polices, return 403 + // isUserSuperAdminOrManageAllAccess only super-admin is checked as manage all access is not applicable for user + isUserSuperAdmin, err = impl.IsSuperAdmin(int(userInfo.Id)) + if err != nil { + return false, isUserSuperAdmin, err + } + return false, isUserSuperAdmin, nil +} diff --git a/pkg/auth/user/UserService_test.go b/pkg/auth/user/UserService_test.go index 9766cb63d9..7c5a9b610c 100644 --- a/pkg/auth/user/UserService_test.go +++ b/pkg/auth/user/UserService_test.go @@ -17,10 +17,10 @@ package user import ( + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "testing" "time" - "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/internal/util" repository2 "github.com/devtron-labs/devtron/pkg/auth/user/repository" repomock2 "github.com/devtron-labs/devtron/pkg/auth/user/repository/RepositoryMocks" diff --git a/pkg/auth/user/adapter/adapter.go b/pkg/auth/user/adapter/adapter.go index 14429fa2c4..f20cee0685 100644 --- a/pkg/auth/user/adapter/adapter.go +++ b/pkg/auth/user/adapter/adapter.go @@ -17,24 +17,76 @@ package adapter import ( - "github.com/devtron-labs/devtron/api/bean" - "github.com/devtron-labs/devtron/pkg/auth/user/repository" + "github.com/devtron-labs/devtron/internal/constants" + "github.com/devtron-labs/devtron/internal/util" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "strings" - "time" ) -func GetLastLoginTime(model repository.UserModel) time.Time { - lastLoginTime := time.Time{} - if model.UserAudit != nil { - lastLoginTime = model.UserAudit.UpdatedOn +func GetCasbinGroupPolicyForEmailAndRoleOnly(emailId string, role string) bean.Policy { + return bean.Policy{ + Type: "g", + Sub: bean.Subject(emailId), + Obj: bean.Object(role), } - return lastLoginTime } -func CreateRestrictedGroup(roleGroupName string, hasSuperAdminPermission bool) bean.RestrictedGroup { +func CreateRestrictedGroup(roleGroupName string, hasSuperAdminPermission bool) bean2.RestrictedGroup { trimmedGroup := strings.TrimPrefix(roleGroupName, "group:") - return bean.RestrictedGroup{ + return bean2.RestrictedGroup{ Group: trimmedGroup, HasSuperAdminPermission: hasSuperAdminPermission, } } + +func BuildUserInfoResponseAdapter(requestUserInfo *bean2.UserInfo, emailId string) *bean2.UserInfo { + return &bean2.UserInfo{ + Id: requestUserInfo.Id, + EmailId: emailId, + Groups: requestUserInfo.Groups, + RoleFilters: requestUserInfo.RoleFilters, + SuperAdmin: requestUserInfo.SuperAdmin, + UserRoleGroup: requestUserInfo.UserRoleGroup, + } +} + +func BuildSelfRegisterDto(userInfo *bean2.UserInfo) *bean2.SelfRegisterDto { + return &bean2.SelfRegisterDto{ + UserInfo: userInfo, + } +} + +func BuildRoleFilterFromRoleF(roleF bean2.RoleFilter) bean2.RoleFilter { + return bean2.RoleFilter{ + Entity: roleF.Entity, + Team: roleF.Team, + Environment: roleF.Environment, + EntityName: roleF.EntityName, + Action: roleF.Action, + AccessType: roleF.AccessType, + Cluster: roleF.Cluster, + Namespace: roleF.Namespace, + Group: roleF.Group, + Kind: roleF.Kind, + Resource: roleF.Resource, + Workflow: roleF.Workflow, + } +} + +func GetNoTokenProvidedError() error { + return &util.ApiError{ + Code: constants.UserNoTokenProvided, + InternalMessage: bean2.NoTokenProvidedMessage, + } +} + +func BuildUserInfo(id int32, emailId string, userGroupMapDto *bean2.UserGroupMapDto) bean2.UserInfo { + return bean2.UserInfo{ + Id: id, + EmailId: emailId, + RoleFilters: make([]bean2.RoleFilter, 0), + Groups: make([]string, 0), + UserRoleGroup: make([]bean2.UserRoleGroup, 0), + } +} diff --git a/pkg/auth/user/adapter/adapter_ent.go b/pkg/auth/user/adapter/adapter_ent.go new file mode 100644 index 0000000000..2eb73f9044 --- /dev/null +++ b/pkg/auth/user/adapter/adapter_ent.go @@ -0,0 +1,57 @@ +package adapter + +import ( + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" + bean3 "github.com/devtron-labs/devtron/pkg/auth/user/repository/bean" +) + +func GetCasbinGroupPolicy(emailId string, role string, twcDto *bean2.TimeoutWindowConfigDto) bean.Policy { + return bean.Policy{ + Type: "g", + Sub: bean.Subject(emailId), + Obj: bean.Object(role), + } +} + +func BuildClusterRoleFieldsDto(entity, accessType, cluster, namespace, group, kind, resource, actionType, subAction string) *bean3.RoleModelFieldsDto { + return &bean3.RoleModelFieldsDto{ + Entity: entity, + AccessType: accessType, + Cluster: cluster, + Namespace: namespace, + Group: group, + Kind: kind, + Resource: resource, + Action: actionType, + } +} + +func BuildSuperAdminRoleFieldsDto() *bean3.RoleModelFieldsDto { + return &bean3.RoleModelFieldsDto{ + Action: bean2.SUPER_ADMIN, + } +} + +func BuildOtherRoleFieldsDto(entity, team, entityName, environment, actionType, accessType string, OldValues bool, subAction string, approver bool) *bean3.RoleModelFieldsDto { + return &bean3.RoleModelFieldsDto{ + Entity: entity, + Team: team, + App: entityName, + Env: environment, + Action: actionType, + AccessType: accessType, + OldValues: OldValues, + } +} +func BuildJobsRoleFieldsDto(entity, team, entityName, environment, actionType, accessType, workflow, subAction string) *bean3.RoleModelFieldsDto { + return &bean3.RoleModelFieldsDto{ + Entity: entity, + Team: team, + App: entityName, + Env: environment, + Action: actionType, + AccessType: accessType, + Workflow: workflow, + } +} diff --git a/api/bean/UserRequest.go b/pkg/auth/user/bean/UserRequest.go similarity index 88% rename from api/bean/UserRequest.go rename to pkg/auth/user/bean/UserRequest.go index bc670469c7..a9dfb8f0bd 100644 --- a/api/bean/UserRequest.go +++ b/pkg/auth/user/bean/UserRequest.go @@ -18,7 +18,6 @@ package bean import ( "encoding/json" - "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/sql" "time" ) @@ -35,7 +34,6 @@ type UserInfo struct { Roles []string `json:"roles,omitempty"` AccessToken string `json:"access_token,omitempty"` RoleFilters []RoleFilter `json:"roleFilters"` - Status string `json:"status,omitempty"` Groups []string `json:"groups"` // this will be deprecated in future do not use UserRoleGroup []UserRoleGroup `json:"userRoleGroups"` // role group with metadata SuperAdmin bool `json:"superAdmin,notnull"` @@ -47,12 +45,16 @@ type UserInfo struct { UserId int32 `json:"-"` // created or modified user id } +func (ui *UserInfo) SetEntityAudit(auditLog sql.AuditLog) *UserInfo { + // doing nothing, ent implementation differs + return ui +} + type RoleGroup struct { Id int32 `json:"id" validate:"number"` Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` RoleFilters []RoleFilter `json:"roleFilters"` - Status string `json:"status,omitempty"` SuperAdmin bool `json:"superAdmin"` UserId int32 `json:"-"` // created or modified user id } @@ -125,14 +127,11 @@ const ( type PolicyType int const ( - POLICY_DIRECT PolicyType = 1 - POLICY_GROUP PolicyType = 1 - SUPERADMIN = "role:super-admin___" - APP_ACCESS_TYPE_HELM = "helm-app" - USER_TYPE_API_TOKEN = "apiToken" - CHART_GROUP_ENTITY = "chart-group" - CLUSTER_ENTITIY = "cluster" - ACTION_SUPERADMIN = "super-admin" + POLICY_DIRECT PolicyType = 1 + POLICY_GROUP PolicyType = 1 + SUPERADMIN = "role:super-admin___" + USER_TYPE_API_TOKEN = "apiToken" + ACTION_SUPERADMIN = "super-admin" ) type UserListingResponse struct { @@ -151,13 +150,13 @@ type RestrictedGroup struct { } type ListingRequest struct { - SearchKey string `json:"searchKey"` - SortOrder bean.SortOrder `json:"sortOrder"` - SortBy bean.SortBy `json:"sortBy"` - Offset int `json:"offset"` - Size int `json:"size"` - ShowAll bool `json:"showAll"` - CountCheck bool `json:"-"` + SearchKey string `json:"searchKey"` + SortOrder SortOrder `json:"sortOrder"` + SortBy SortBy `json:"sortBy"` + Offset int `json:"offset"` + Size int `json:"size"` + ShowAll bool `json:"showAll"` + CountCheck bool `json:"-"` } type BulkDeleteRequest struct { @@ -206,3 +205,13 @@ func (pa *UserPermissionsAuditDto) WithEntityAudit(entityAudit sql.AuditLog) *Us pa.EntityAudit = entityAudit return pa } + +type SelfRegisterDto struct { + UserInfo *UserInfo +} + +type TimeoutWindowConfigDto struct { +} + +type UserGroupMapDto struct { +} diff --git a/pkg/auth/user/bean/bean.go b/pkg/auth/user/bean/bean.go index 4ecd4e94b2..01235fce1a 100644 --- a/pkg/auth/user/bean/bean.go +++ b/pkg/auth/user/bean/bean.go @@ -66,12 +66,20 @@ const ( const ( DEVTRON_APP = "devtron-app" APP_ACCESS_TYPE_HELM = "helm-app" + EmptyAccessType = "" ) const ( VALIDATION_FAILED_ERROR_MSG string = "validation failed: group name with , is not allowed" ) +// messages constants +const ( + NoTokenProvidedMessage = "no token provided" + RoleAlreadyExistMessage = "role already exist" + PolicyAlreadyExistMessage = "policy already exist" +) + type RbacRoleDto struct { Id int `json:"id"` // id of the default role RoleName string `json:"roleName"` diff --git a/pkg/auth/user/helper/helper.go b/pkg/auth/user/helper/helper.go index df2a353197..ae0da45989 100644 --- a/pkg/auth/user/helper/helper.go +++ b/pkg/auth/user/helper/helper.go @@ -19,11 +19,11 @@ package helper import ( "errors" "fmt" - bean2 "github.com/devtron-labs/devtron/api/bean" "github.com/devtron-labs/devtron/internal/util" "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository" "golang.org/x/exp/slices" + "net/http" "strings" ) @@ -74,7 +74,7 @@ func ExtractTokenNameFromEmail(email string) (string, error) { return splitData[1], nil } -func CreateErrorMessageForUserRoleGroups(restrictedGroups []bean2.RestrictedGroup) (string, string) { +func CreateErrorMessageForUserRoleGroups(restrictedGroups []bean.RestrictedGroup) (string, string) { var restrictedGroupsWithSuperAdminPermission string var restrictedGroupsWithoutSuperAdminPermission string var errorMessageForGroupsWithoutSuperAdmin string @@ -106,9 +106,32 @@ func GetCasbinNameFromRoleGroupName(name string) string { func CheckIfSuperAdminFromRoles(roles []*repository.RoleModel) bool { for _, role := range roles { - if role.Role == bean2.SUPERADMIN { + if role.Role == bean.SUPERADMIN { return true } } return false } + +func ValidateRoleFilters(rolefilters []bean.RoleFilter) error { + invalid := false + for _, roleFilter := range rolefilters { + if len(roleFilter.Team) > 0 && len(roleFilter.Action) > 0 { + // + } else if len(roleFilter.Entity) > 0 { //this will pass roleFilter for clusterEntity as well as chart-group + // + } else { + invalid = true + } + } + if invalid { + err := &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"} + return err + } + return nil +} + +// ValidateUserRoleGroupRequest returns nil for oss implementation +func ValidateUserRoleGroupRequest(userRoleGroups []bean.UserRoleGroup) error { + return nil +} diff --git a/pkg/auth/user/mocks/UserService.go b/pkg/auth/user/mocks/UserService.go index 7ebaa91843..43b2924118 100644 --- a/pkg/auth/user/mocks/UserService.go +++ b/pkg/auth/user/mocks/UserService.go @@ -6,10 +6,10 @@ package mock_user import ( context "context" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" http "net/http" reflect "reflect" - bean "github.com/devtron-labs/devtron/api/bean" gomock "github.com/golang/mock/gomock" ) diff --git a/pkg/auth/user/repository/RepositoryMocks/UserRepositoryMock.go b/pkg/auth/user/repository/RepositoryMocks/UserRepositoryMock.go index aafe4bb645..16d4894d42 100644 --- a/pkg/auth/user/repository/RepositoryMocks/UserRepositoryMock.go +++ b/pkg/auth/user/repository/RepositoryMocks/UserRepositoryMock.go @@ -3,7 +3,7 @@ package repomock import ( - bean "github.com/devtron-labs/devtron/api/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository" mock "github.com/stretchr/testify/mock" diff --git a/pkg/auth/user/repository/RoleGroupRepository.go b/pkg/auth/user/repository/RoleGroupRepository.go index 7d2bd4d896..dfeb380419 100644 --- a/pkg/auth/user/repository/RoleGroupRepository.go +++ b/pkg/auth/user/repository/RoleGroupRepository.go @@ -46,6 +46,7 @@ type RoleGroupRepository interface { DeleteRoleGroupRoleMappingsByIds(tx *pg.Tx, ids []int) error GetConnection() (dbConnection *pg.DB) GetRoleGroupListByNames(groupNames []string) ([]*RoleGroup, error) + GetRoleGroupListByIds(ids []int32) ([]*RoleGroup, error) GetRolesByRoleGroupIds(roleGroupIds []int32) ([]*RoleModel, error) GetRolesByGroupCasbinName(groupName string) ([]*RoleModel, error) GetRolesByGroupCasbinNames(groupCasbinNames []string) ([]*RoleModel, error) @@ -248,6 +249,12 @@ func (impl RoleGroupRepositoryImpl) GetRoleGroupListByNames(groupNames []string) return model, err } +func (impl RoleGroupRepositoryImpl) GetRoleGroupListByIds(ids []int32) ([]*RoleGroup, error) { + var model []*RoleGroup + err := impl.dbConnection.Model(&model).Where("id in (?)", pg.In(ids)).Where("active = ?", true).Select() + return model, err +} + func (impl RoleGroupRepositoryImpl) GetRolesByRoleGroupIds(roleGroupIds []int32) ([]*RoleModel, error) { var roleModels []*RoleModel query := "SELECT r.* from roles r" + diff --git a/pkg/auth/user/repository/UserAuthRepository.go b/pkg/auth/user/repository/UserAuthRepository.go index 3146ef1b80..ce8fe2940f 100644 --- a/pkg/auth/user/repository/UserAuthRepository.go +++ b/pkg/auth/user/repository/UserAuthRepository.go @@ -22,6 +22,9 @@ package repository import ( "encoding/json" "fmt" + bean3 "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/adapter" + bean4 "github.com/devtron-labs/devtron/pkg/auth/user/repository/bean" "strings" "time" @@ -44,7 +47,7 @@ type UserAuthRepository interface { GetRolesByGroupId(userId int32) ([]*RoleModel, error) GetAllRole() ([]RoleModel, error) GetRolesByActionAndAccessType(action string, accessType string) ([]RoleModel, error) - GetRoleByFilterForAllTypes(entity, team, app, env, act, accessType, cluster, namespace, group, kind, resource, action string, oldValues bool, workflow string) (RoleModel, error) + GetRoleByFilterForAllTypes(roleFieldDto *bean4.RoleModelFieldsDto) (RoleModel, error) CreateUserRoleMapping(userRoleModel *UserRoleModel, tx *pg.Tx) (*UserRoleModel, error) GetUserRoleMappingByUserId(userId int32) ([]*UserRoleModel, error) GetUserRoleMappingIdsByUserId(userId int32) ([]int, error) @@ -53,7 +56,7 @@ type UserAuthRepository interface { DeleteUserRoleMappingByIds(urmIds []int, tx *pg.Tx) error DeleteUserRoleByRoleId(roleId int, tx *pg.Tx) error DeleteUserRoleByRoleIds(roleIds []int, tx *pg.Tx) error - CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType string, UserId int32) (bool, error, []casbin2.Policy) + CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType string, UserId int32) (bool, error, []bean3.Policy) CreateRoleForSuperAdminIfNotExists(tx *pg.Tx, UserId int32) (bool, error) SyncOrchestratorToCasbin(team string, entityName string, env string, tx *pg.Tx) (bool, error) UpdateTriggerPolicyForTerminalAccess() error @@ -269,23 +272,29 @@ func (impl UserAuthRepositoryImpl) GetRolesByActionAndAccessType(action string, return models, nil } -func (impl UserAuthRepositoryImpl) GetRoleByFilterForAllTypes(entity, team, app, env, act, accessType, cluster, namespace, group, kind, resource, action string, oldValues bool, workflow string) (RoleModel, error) { +func (impl UserAuthRepositoryImpl) GetRoleByFilterForAllTypes(roleFieldDto *bean4.RoleModelFieldsDto) (RoleModel, error) { + entity := roleFieldDto.Entity + action := roleFieldDto.Action switch entity { case bean2.CLUSTER_ENTITIY: { + cluster, namespace, group, kind, resource := roleFieldDto.Cluster, roleFieldDto.Namespace, roleFieldDto.Group, roleFieldDto.Kind, roleFieldDto.Resource return impl.GetRoleForClusterEntity(cluster, namespace, group, kind, resource, action) } case bean2.CHART_GROUP_ENTITY: { - return impl.GetRoleForChartGroupEntity(entity, app, act, accessType) + app, accessType := roleFieldDto.App, roleFieldDto.AccessType + return impl.GetRoleForChartGroupEntity(entity, app, action, accessType) } case bean2.EntityJobs: { - return impl.GetRoleForJobsEntity(entity, team, app, env, act, workflow) + team, app, env, workflow := roleFieldDto.Team, roleFieldDto.App, roleFieldDto.Env, roleFieldDto.Workflow + return impl.GetRoleForJobsEntity(entity, team, app, env, action, workflow) } default: { - return impl.GetRoleForOtherEntity(team, app, env, act, accessType, oldValues) + team, app, env, accessType, oldValues := roleFieldDto.Team, roleFieldDto.App, roleFieldDto.Env, roleFieldDto.AccessType, roleFieldDto.OldValues + return impl.GetRoleForOtherEntity(team, app, env, action, accessType, oldValues) } } return RoleModel{}, nil @@ -376,11 +385,11 @@ func (impl UserAuthRepositoryImpl) DeleteUserRoleByRoleIds(roleIds []int, tx *pg return nil } -func (impl UserAuthRepositoryImpl) CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType string, UserId int32) (bool, error, []casbin2.Policy) { +func (impl UserAuthRepositoryImpl) CreateDefaultPoliciesForAllTypes(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType string, UserId int32) (bool, error, []bean3.Policy) { //not using txn from parent caller because of conflicts in fetching of transactional save dbConnection := impl.dbConnection tx, err := dbConnection.Begin() - var policiesToBeAdded []casbin2.Policy + var policiesToBeAdded []bean3.Policy if err != nil { return false, err, policiesToBeAdded } @@ -475,7 +484,7 @@ func (impl UserAuthRepositoryImpl) CreateDefaultPoliciesForAllTypes(team, entity return false, err, nil } //getting updated role - var roleData bean.RoleData + var roleData bean2.RoleData err = json.Unmarshal([]byte(role), &roleData) if err != nil { impl.Logger.Errorw("decode err", "err", err) @@ -492,7 +501,7 @@ func (impl UserAuthRepositoryImpl) CreateDefaultPoliciesForAllTypes(team, entity return true, nil, policiesToBeAdded } func (impl UserAuthRepositoryImpl) CreateRolesWithAccessTypeAndEntity(team, entityName, env, entity, cluster, namespace, group, kind, resource, actionType, accessType string, UserId int32, role string) (bool, error) { - roleData := bean.RoleData{ + roleData := bean2.RoleData{ Role: role, Entity: entity, Team: team, @@ -520,14 +529,14 @@ func (impl UserAuthRepositoryImpl) CreateRoleForSuperAdminIfNotExists(tx *pg.Tx, } //Creating ROLES - roleModel, err := impl.GetRoleByFilterForAllTypes("", "", "", "", bean2.SUPER_ADMIN, "", "", "", "", "", "", "", false, "") + roleModel, err := impl.GetRoleByFilterForAllTypes(adapter.BuildSuperAdminRoleFieldsDto()) if err != nil && err != pg.ErrNoRows { return false, err } if roleModel.Id == 0 || err == pg.ErrNoRows { roleManager := "{\r\n \"role\": \"role:super-admin___\",\r\n \"casbinSubjects\": [\r\n \"role:super-admin___\"\r\n ],\r\n \"team\": \"\",\r\n \"entityName\": \"\",\r\n \"environment\": \"\",\r\n \"action\": \"super-admin\"\r\n}" - var roleManagerData bean.RoleData + var roleManagerData bean2.RoleData err = json.Unmarshal([]byte(roleManager), &roleManagerData) if err != nil { impl.Logger.Errorw("decode err", "err", err) @@ -545,7 +554,7 @@ func (impl UserAuthRepositoryImpl) CreateRoleForSuperAdminIfNotExists(tx *pg.Tx, return true, nil } -func (impl UserAuthRepositoryImpl) createRole(roleData *bean.RoleData, UserId int32) (bool, error) { +func (impl UserAuthRepositoryImpl) createRole(roleData *bean2.RoleData, UserId int32) (bool, error) { roleModel := &RoleModel{ Role: roleData.Role, Entity: roleData.Entity, @@ -623,7 +632,7 @@ func (impl UserAuthRepositoryImpl) SyncOrchestratorToCasbin(team string, entityN } //for START in Casbin Object Ends Here - var policies []casbin2.Policy + var policies []bean3.Policy var policiesTrigger bean.PolicyRequest err = json.Unmarshal([]byte(triggerPolicies), &policiesTrigger) if err != nil { @@ -793,7 +802,7 @@ func (impl UserAuthRepositoryImpl) UpdateDefaultPolicyByRoleType(newPolicy strin return nil } -func (impl UserAuthRepositoryImpl) GetDiffBetweenPolicies(oldPolicy string, newPolicy string) (addedPolicies []casbin2.Policy, deletedPolicies []casbin2.Policy, err error) { +func (impl UserAuthRepositoryImpl) GetDiffBetweenPolicies(oldPolicy string, newPolicy string) (addedPolicies []bean3.Policy, deletedPolicies []bean3.Policy, err error) { var oldPolicyObj bean.PolicyRequest err = json.Unmarshal([]byte(oldPolicy), &oldPolicyObj) if err != nil { @@ -842,7 +851,7 @@ func (impl UserAuthRepositoryImpl) GetDiffBetweenPolicies(oldPolicy string, newP return addedPolicies, deletedPolicies, nil } -func (impl UserAuthRepositoryImpl) GetUpdatedAddedOrDeletedPolicies(policies []casbin2.Policy, rolePolicyDetails RolePolicyDetails) (bean.PolicyRequest, error) { +func (impl UserAuthRepositoryImpl) GetUpdatedAddedOrDeletedPolicies(policies []bean3.Policy, rolePolicyDetails RolePolicyDetails) (bean.PolicyRequest, error) { var policyResp bean.PolicyRequest var policyReq bean.PolicyRequest policyReq.Data = policies diff --git a/pkg/auth/user/repository/UserRepository.go b/pkg/auth/user/repository/UserRepository.go index b5c3cc625b..412e254f59 100644 --- a/pkg/auth/user/repository/UserRepository.go +++ b/pkg/auth/user/repository/UserRepository.go @@ -21,7 +21,6 @@ package repository import ( "fmt" - "github.com/devtron-labs/devtron/api/bean" userBean "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository/helper" "github.com/devtron-labs/devtron/pkg/auth/user/util" @@ -34,15 +33,15 @@ import ( type UserRepository interface { CreateUser(userModel *UserModel, tx *pg.Tx) (*UserModel, error) UpdateUser(userModel *UserModel, tx *pg.Tx) (*UserModel, error) - UpdateToInactiveByIds(ids []int32, tx *pg.Tx, loggedInUserId int32) error + UpdateToInactiveByIds(ids []int32, tx *pg.Tx, loggedInUserId int32, recordedTime time.Time) error GetById(id int32) (*UserModel, error) GetEmailByIds(ids []int32) ([]string, error) GetByIdIncludeDeleted(id int32) (*UserModel, error) GetAllExcludingApiTokenUser() ([]UserModel, error) GetAllExecutingQuery(query string, queryParams []interface{}) ([]UserModel, error) //GetAllUserRoleMappingsForRoleId(roleId int) ([]UserRoleModel, error) - FetchActiveUserByEmail(email string) (bean.UserInfo, error) - FetchUserDetailByEmail(email string) (bean.UserInfo, error) + FetchActiveUserByEmail(email string) (userBean.UserInfo, error) + FetchUserDetailByEmail(email string) (userBean.UserInfo, error) GetByIds(ids []int32) ([]UserModel, error) GetConnection() (dbConnection *pg.DB) FetchUserMatchesByEmailIdExcludingApiTokenUser(email string) ([]UserModel, error) @@ -106,7 +105,7 @@ func (impl UserRepositoryImpl) UpdateUser(userModel *UserModel, tx *pg.Tx) (*Use return userModel, nil } -func (impl UserRepositoryImpl) UpdateToInactiveByIds(ids []int32, tx *pg.Tx, loggedInUserId int32) error { +func (impl UserRepositoryImpl) UpdateToInactiveByIds(ids []int32, tx *pg.Tx, loggedInUserId int32, recordedTime time.Time) error { var model []*UserModel _, err := tx.Model(&model). Set("active = ?", false). @@ -157,7 +156,7 @@ func (impl UserRepositoryImpl) GetAllExcludingApiTokenUser() ([]UserModel, error var userModel []UserModel err := impl.dbConnection.Model(&userModel). Where("active = ?", true). - Where("user_type is NULL or user_type != ?", bean.USER_TYPE_API_TOKEN). + Where("user_type is NULL or user_type != ?", userBean.USER_TYPE_API_TOKEN). Order("updated_on desc").Select() for i, user := range userModel { userModel[i].EmailId = util.ConvertEmailToLowerCase(user.EmailId) @@ -178,8 +177,8 @@ func (impl UserRepositoryImpl) GetAllExecutingQuery(query string, queryParams [] return userModel, err } -func (impl UserRepositoryImpl) FetchActiveUserByEmail(email string) (bean.UserInfo, error) { - var users bean.UserInfo +func (impl UserRepositoryImpl) FetchActiveUserByEmail(email string) (userBean.UserInfo, error) { + var users userBean.UserInfo emailSearchQuery, queryParams := helper.GetEmailSearchQuery("u", email) query := fmt.Sprintf("SELECT u.id, u.email_id, u.access_token, u.user_type FROM users u"+ @@ -193,10 +192,10 @@ func (impl UserRepositoryImpl) FetchActiveUserByEmail(email string) (bean.UserIn return users, nil } -func (impl UserRepositoryImpl) FetchUserDetailByEmail(email string) (bean.UserInfo, error) { +func (impl UserRepositoryImpl) FetchUserDetailByEmail(email string) (userBean.UserInfo, error) { //impl.Logger.Info("reached at FetchUserDetailByEmail:") - var users []bean.UserRole - var userFinal bean.UserInfo + var users []userBean.UserRole + var userFinal userBean.UserInfo emailSearchQuery, queryParams := helper.GetEmailSearchQuery("u", email) query := fmt.Sprintf("SELECT u.id, u.email_id, u.user_type, r.role FROM users u"+ @@ -236,7 +235,7 @@ func (impl UserRepositoryImpl) FetchUserMatchesByEmailIdExcludingApiTokenUser(em var model []UserModel err := impl.dbConnection.Model(&model). Where("email_id ilike (?)", "%"+email+"%"). - Where("user_type is NULL or user_type != ?", bean.USER_TYPE_API_TOKEN). + Where("user_type is NULL or user_type != ?", userBean.USER_TYPE_API_TOKEN). Where("active = ?", true).Select() for i, m := range model { model[i].EmailId = util.ConvertEmailToLowerCase(m.EmailId) diff --git a/pkg/auth/user/repository/adapter/adapter.go b/pkg/auth/user/repository/adapter/adapter.go new file mode 100644 index 0000000000..0f031ea582 --- /dev/null +++ b/pkg/auth/user/repository/adapter/adapter.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024. Devtron Inc. + */ + +package adapter + +import ( + bean "github.com/devtron-labs/devtron/pkg/auth/user/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/repository" + "github.com/devtron-labs/devtron/pkg/sql" + "time" +) + +func GetLastLoginTime(model repository.UserModel) time.Time { + lastLoginTime := time.Time{} + if model.UserAudit != nil { + lastLoginTime = model.UserAudit.UpdatedOn + } + return lastLoginTime +} + +func GetUserModelBasicAdapter(emailId, accessToken, userType string) *repository.UserModel { + model := &repository.UserModel{ + EmailId: emailId, + AccessToken: accessToken, + UserType: userType, + } + return model +} + +func GetUserRoleModelAdapter(userId, userLoggedInId int32, roleId int, twcConfigDto *bean.TimeoutWindowConfigDto) *repository.UserRoleModel { + return &repository.UserRoleModel{ + UserId: userId, + RoleId: roleId, + AuditLog: sql.NewDefaultAuditLog(userLoggedInId), + } +} + +func GetRoleGroupRoleMappingModelAdapter(roleGroupId int32, roleId int, userId int32) *repository.RoleGroupRoleMapping { + return &repository.RoleGroupRoleMapping{ + RoleGroupId: roleGroupId, + RoleId: roleId, + AuditLog: sql.NewDefaultAuditLog(userId), + } +} diff --git a/pkg/auth/user/repository/bean/bean.go b/pkg/auth/user/repository/bean/bean.go new file mode 100644 index 0000000000..eb1e4f8d7f --- /dev/null +++ b/pkg/auth/user/repository/bean/bean.go @@ -0,0 +1,17 @@ +package bean + +type RoleModelFieldsDto struct { + Entity, + Team, + App, + Env, + AccessType, + Cluster, + Namespace, + Group, + Kind, + Resource string + Action string + OldValues bool + Workflow string +} diff --git a/pkg/auth/user/repository/helper/UserRepositoryQueryBuilder.go b/pkg/auth/user/repository/helper/UserRepositoryQueryBuilder.go index 976bbfcd6b..f8d559eb19 100644 --- a/pkg/auth/user/repository/helper/UserRepositoryQueryBuilder.go +++ b/pkg/auth/user/repository/helper/UserRepositoryQueryBuilder.go @@ -18,13 +18,12 @@ package helper import ( "fmt" - "github.com/devtron-labs/devtron/api/bean" bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/util" ) -func GetQueryForUserListingWithFilters(req *bean.ListingRequest) (string, []interface{}) { - whereCondition := fmt.Sprintf("where active = %t AND (user_type is NULL or user_type != '%s') ", true, bean.USER_TYPE_API_TOKEN) +func GetQueryForUserListingWithFilters(req *bean2.ListingRequest) (string, []interface{}) { + whereCondition := fmt.Sprintf("where active = %t AND (user_type is NULL or user_type != '%s') ", true, bean2.USER_TYPE_API_TOKEN) orderCondition := "" var queryParams []interface{} if len(req.SearchKey) > 0 { @@ -67,13 +66,13 @@ func GetQueryForUserListingWithFilters(req *bean.ListingRequest) (string, []inte } func GetQueryForAllUserWithAudit() string { - whereCondition := fmt.Sprintf("where active = %t AND (user_type is NULL or user_type != '%s') ", true, bean.USER_TYPE_API_TOKEN) + whereCondition := fmt.Sprintf("where active = %t AND (user_type is NULL or user_type != '%s') ", true, bean2.USER_TYPE_API_TOKEN) orderCondition := fmt.Sprintf("order by user_model.updated_on %s", bean2.Desc) query := fmt.Sprintf(`SELECT "user_model".*, "user_audit"."id" AS "user_audit__id", "user_audit"."updated_on" AS "user_audit__updated_on","user_audit"."user_id" AS "user_audit__user_id" ,"user_audit"."created_on" AS "user_audit__created_on" from users As "user_model" LEFT JOIN user_audit As "user_audit" on "user_audit"."user_id" = "user_model"."id" %s %s;`, whereCondition, orderCondition) return query } -func GetQueryForGroupListingWithFilters(req *bean.ListingRequest) (string, []interface{}) { +func GetQueryForGroupListingWithFilters(req *bean2.ListingRequest) (string, []interface{}) { var queryParams []interface{} whereCondition := " where active = ? " queryParams = append(queryParams, true) diff --git a/pkg/auth/user/repository/mocks/UserRepository.go b/pkg/auth/user/repository/mocks/UserRepository.go index f398adb9f2..8db607178a 100644 --- a/pkg/auth/user/repository/mocks/UserRepository.go +++ b/pkg/auth/user/repository/mocks/UserRepository.go @@ -3,7 +3,7 @@ package mocks import ( - bean "github.com/devtron-labs/devtron/api/bean" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" "github.com/devtron-labs/devtron/pkg/auth/user/repository" mock "github.com/stretchr/testify/mock" diff --git a/pkg/userResource/UserResourceExtendedService.go b/pkg/userResource/UserResourceExtendedService.go new file mode 100644 index 0000000000..358a5000e3 --- /dev/null +++ b/pkg/userResource/UserResourceExtendedService.go @@ -0,0 +1,127 @@ +package userResource + +import ( + "context" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/internal/util" + "github.com/devtron-labs/devtron/pkg/app" + "github.com/devtron-labs/devtron/pkg/appStore/chartGroup" + "github.com/devtron-labs/devtron/pkg/appWorkflow" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + "github.com/devtron-labs/devtron/pkg/cluster" + "github.com/devtron-labs/devtron/pkg/cluster/environment" + application2 "github.com/devtron-labs/devtron/pkg/k8s/application" + "github.com/devtron-labs/devtron/pkg/team" + "github.com/devtron-labs/devtron/pkg/userResource/adapter" + bean5 "github.com/devtron-labs/devtron/pkg/userResource/bean" + "github.com/devtron-labs/devtron/pkg/userResource/helper" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" + "github.com/devtron-labs/devtron/util/rbac" + "go.uber.org/zap" + "net/http" +) + +type UserResourceExtendedServiceImpl struct { + logger *zap.SugaredLogger + chartGroupService chartGroup.ChartGroupService + appListingService app.AppListingService + appWorkflowService appWorkflow.AppWorkflowService + *UserResourceServiceImpl +} + +func NewUserResourceExtendedServiceImpl(logger *zap.SugaredLogger, teamService team.TeamService, + envService environment.EnvironmentService, + appService app.AppCrudOperationService, + chartGroupService chartGroup.ChartGroupService, + appListingService app.AppListingService, + appWorkflowService appWorkflow.AppWorkflowService, + k8sApplicationService application2.K8sApplicationService, + clusterService cluster.ClusterService, + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil, + enforcerUtil rbac.EnforcerUtil, + enforcer casbin.Enforcer) *UserResourceExtendedServiceImpl { + return &UserResourceExtendedServiceImpl{ + logger: logger, + chartGroupService: chartGroupService, + appListingService: appListingService, + appWorkflowService: appWorkflowService, + UserResourceServiceImpl: NewUserResourceServiceImpl(logger, teamService, envService, clusterService, k8sApplicationService, enforcerUtil, rbacEnforcementUtil, enforcer, appService), + } + +} + +func (impl *UserResourceExtendedServiceImpl) GetResourceOptions(context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.UserResourceResponseDto, error) { + err := helper.ValidateResourceOptionReqBean(reqBean) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + f := getAllResourceOptionsExtendedFunc(bean5.UserResourceKind(params.Kind), bean5.Version(params.Version)) + if f == nil { + impl.logger.Errorw("error encountered in GetResourceOptions, not supported kind", "params", params) + return nil, util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.RequestInvalidKindVersionErrMessage, bean5.RequestInvalidKindVersionErrMessage) + } + data, err := f(impl, context, token, reqBean, params) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + // rbac function get and enforce at service level + f2 := getResourceOptionRbacExtendedFunc(bean5.UserResourceKind(params.Kind), bean5.Version(params.Version), reqBean.Entity, reqBean.AccessType) + if f2 == nil { + impl.logger.Errorw("error encountered in GetResourceOptions, not supported kind for rbac", "params", params) + return nil, util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.RequestInvalidKindVersionErrMessage, bean5.RequestInvalidKindVersionErrMessage) + } + finalData, err := f2(impl, token, params, data) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + return finalData, nil +} + +func (impl *UserResourceExtendedServiceImpl) getEnvResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // get env resource options + env, err := impl.envService.GetEnvironmentListForAutocomplete(false) + if err != nil { + impl.logger.Errorw("error in getEnvResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithEnvResp(env), nil + +} + +func (impl *UserResourceExtendedServiceImpl) getChartGroupResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // get chart group resource options + // max is passed as 0 to get all chart groups, default behaviour + chartGroups, err := impl.chartGroupService.GetChartGroupList(0) + if err != nil { + impl.logger.Errorw("error in getChartGroupResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithChartGroupResp(chartGroups), nil +} + +func (impl *UserResourceExtendedServiceImpl) getJobsResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + fetchJobListingRequest := adapter.BuildFetchAppListingReqForJobFromDto(reqBean) + jobs, err := impl.appListingService.FetchJobs(fetchJobListingRequest) + if err != nil { + impl.logger.Errorw("error in getJobsResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithJobsResp(jobs), nil +} + +func (impl *UserResourceExtendedServiceImpl) getAppWfsResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + workflows, err := impl.appWorkflowService.FindAllWorkflowsForApps(*reqBean.WorkflowNamesRequest) + if err != nil { + impl.logger.Errorw("error in getAppWfsResourceOptions", "err", err) + return nil, err + } + + return bean5.NewResourceOptionsDto().WithAppWfsResp(workflows), nil +} diff --git a/pkg/userResource/UserResourceRbacExtendedService.go b/pkg/userResource/UserResourceRbacExtendedService.go new file mode 100644 index 0000000000..0f6c30205d --- /dev/null +++ b/pkg/userResource/UserResourceRbacExtendedService.go @@ -0,0 +1,94 @@ +package userResource + +import ( + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + bean3 "github.com/devtron-labs/devtron/pkg/cluster/environment/bean" + bean2 "github.com/devtron-labs/devtron/pkg/team/bean" + "github.com/devtron-labs/devtron/pkg/userResource/adapter" + "github.com/devtron-labs/devtron/pkg/userResource/bean" +) + +func (impl *UserResourceServiceImpl) enforceRbacForTeamForDevtronApp(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + result := make([]bean2.TeamRequest, 0, len(resourceOptions.TeamsResp)) + isSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if isSuperAdmin { + result = resourceOptions.TeamsResp + } else { + for _, item := range resourceOptions.TeamsResp { + if ok := impl.enforcer.Enforce(token, casbin.ResourceTeam, casbin.ActionGet, item.Name); ok { + result = append(result, item) + } + } + } + return adapter.BuildUserResourceResponseDto(result), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForTeamForJobs(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to access team for jobs") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.TeamsResp), nil +} +func (impl *UserResourceServiceImpl) enforceRbacForEnvForDevtronApp(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + result := make([]bean3.EnvironmentBean, 0, len(resourceOptions.EnvResp)) + isSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if isSuperAdmin { + result = resourceOptions.EnvResp + } else { + result = impl.rbacEnforcementUtil.CheckAuthorisationForEnvs(token, resourceOptions.EnvResp) + } + + return adapter.BuildUserResourceResponseDto(result), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForEnvForJobs(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to access env for jobs") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.EnvResp), nil +} +func (impl *UserResourceServiceImpl) enforceRbacForDevtronApps(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + result := impl.rbacEnforcementUtil.CheckAuthorisationOnApp(token, resourceOptions.TeamAppResp) + return adapter.BuildUserResourceResponseDto(result), nil +} +func (impl *UserResourceServiceImpl) enforceRbacForHelmAppsListing(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForHelmAppsListing") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.TeamAppResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForJobs(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForJobs") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.JobsResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForChartGroup(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") || + impl.enforcer.Enforce(token, casbin.ResourceChartGroup, casbin.ActionGet, "") // doing this for manager check as chart group is by default shown to every user + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForChartGroup") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ChartGroupResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForJobsWfs(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized enforceRbacForJobsWfs") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.AppWfsResp), nil +} diff --git a/pkg/userResource/UserResourceRbacService.go b/pkg/userResource/UserResourceRbacService.go new file mode 100644 index 0000000000..6e0e75e891 --- /dev/null +++ b/pkg/userResource/UserResourceRbacService.go @@ -0,0 +1,62 @@ +package userResource + +import ( + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + "github.com/devtron-labs/devtron/pkg/userResource/adapter" + "github.com/devtron-labs/devtron/pkg/userResource/bean" +) + +func (impl *UserResourceServiceImpl) enforceRbacForTeamForHelmApp(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForTeamForHelmApp") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.TeamsResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForEnvForHelmApp(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForEnvForHelmApp") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.HelmEnvResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForClusterList(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForClusterList") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ClusterResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForClusterApiResource(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForClusterApiResource") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ApiResourcesResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForClusterResourceList(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForClusterResourceList") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.ClusterResourcesResp), nil +} + +func (impl *UserResourceServiceImpl) enforceRbacForClusterNamespaces(token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionUpdate, "*") + if !isAuthorised { + impl.logger.Errorw("user is unauthorized to enforceRbacForClusterNamespaces") + return adapter.BuildNullDataUserResourceResponseDto(), nil + } + return adapter.BuildUserResourceResponseDto(resourceOptions.NameSpaces), nil +} diff --git a/pkg/userResource/UserResourceService.go b/pkg/userResource/UserResourceService.go new file mode 100644 index 0000000000..53fffa9ca6 --- /dev/null +++ b/pkg/userResource/UserResourceService.go @@ -0,0 +1,181 @@ +package userResource + +import ( + "context" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + app2 "github.com/devtron-labs/devtron/internal/sql/repository/app" + "github.com/devtron-labs/devtron/internal/util" + "github.com/devtron-labs/devtron/pkg/app" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + "github.com/devtron-labs/devtron/pkg/auth/user/bean" + "github.com/devtron-labs/devtron/pkg/cluster" + "github.com/devtron-labs/devtron/pkg/cluster/environment" + application2 "github.com/devtron-labs/devtron/pkg/k8s/application" + bean4 "github.com/devtron-labs/devtron/pkg/k8s/bean" + "github.com/devtron-labs/devtron/pkg/team" + bean5 "github.com/devtron-labs/devtron/pkg/userResource/bean" + "github.com/devtron-labs/devtron/pkg/userResource/helper" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" + "github.com/devtron-labs/devtron/util/rbac" + "go.uber.org/zap" + "net/http" +) + +type UserResourceService interface { + GetResourceOptions(context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, + params *apiBean.PathParams) (*bean5.UserResourceResponseDto, error) +} +type UserResourceServiceImpl struct { + logger *zap.SugaredLogger + teamService team.TeamService + envService environment.EnvironmentService + clusterService cluster.ClusterService + k8sApplicationService application2.K8sApplicationService + enforcerUtil rbac.EnforcerUtil + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil + enforcer casbin.Enforcer + appService app.AppCrudOperationService +} + +func NewUserResourceServiceImpl(logger *zap.SugaredLogger, + teamService team.TeamService, + envService environment.EnvironmentService, + clusterService cluster.ClusterService, + k8sApplicationService application2.K8sApplicationService, + enforcerUtil rbac.EnforcerUtil, + rbacEnforcementUtil commonEnforcementFunctionsUtil.CommonEnforcementUtil, + enforcer casbin.Enforcer, + appService app.AppCrudOperationService) *UserResourceServiceImpl { + return &UserResourceServiceImpl{ + logger: logger, + teamService: teamService, + envService: envService, + clusterService: clusterService, + k8sApplicationService: k8sApplicationService, + enforcerUtil: enforcerUtil, + rbacEnforcementUtil: rbacEnforcementUtil, + enforcer: enforcer, + appService: appService, + } +} + +func (impl *UserResourceServiceImpl) GetResourceOptions(context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.UserResourceResponseDto, error) { + err := helper.ValidateResourceOptionReqBean(reqBean) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + // validation based on kind ,sub kind and entity and access type + f := getAllResourceOptionsFunc(bean5.UserResourceKind(params.Kind), bean5.Version(params.Version)) + if f == nil { + impl.logger.Errorw("error encountered in GetResourceOptions, not supported kind", "params", params) + return nil, util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.RequestInvalidKindVersionErrMessage, bean5.RequestInvalidKindVersionErrMessage) + } + data, err := f(impl, context, token, reqBean, params) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + // rbac function get and enforce at service level + f2 := getResourceOptionRbacFunc(bean5.UserResourceKind(params.Kind), bean5.Version(params.Version), reqBean.Entity, reqBean.AccessType) + if f2 == nil { + impl.logger.Errorw("error encountered in GetResourceOptions, not supported kind for rbac", "params", params) + return nil, util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.RequestInvalidKindVersionErrMessage, bean5.RequestInvalidKindVersionErrMessage) + } + finalData, err := f2(impl, token, params, data) + if err != nil { + impl.logger.Errorw("error in GetResourceOptions", "err", err, "reqBean", reqBean) + return nil, err + } + return finalData, nil +} + +func (impl *UserResourceServiceImpl) getTeamResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // get team resource options + teams, err := impl.teamService.FetchAllActive() + if err != nil { + impl.logger.Errorw("error in getTeamResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithTeamsResp(teams), nil + +} +func (impl *UserResourceServiceImpl) getAppResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // empty app type is devtron-app + apps, err := impl.appService.GetAppListByTeamIds(reqBean.TeamIds, app2.DevtronApp) + if err != nil { + impl.logger.Errorw("error encountered in getAppResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithTeamAppResp(apps), nil +} +func (impl *UserResourceServiceImpl) getHelmAppResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + apps, err := impl.appService.GetAppListByTeamIds(reqBean.TeamIds, app2.DevtronChart) + if err != nil { + impl.logger.Errorw("error encountered in getAppResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithTeamAppResp(apps), nil +} + +func (impl *UserResourceServiceImpl) getHelmEnvResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + + // get helm env resource options + env, err := impl.envService.GetCombinedEnvironmentListForDropDown(token, true, impl.rbacEnforcementUtil.CheckAuthorizationByEmailInBatchForGlobalEnvironment) + if err != nil { + impl.logger.Errorw("error in getEnvResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithHelmEnvResp(env), nil +} + +func (impl *UserResourceServiceImpl) getClusterResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // System User id is passed as 1 as rbac enforcement is handled globally at bottom level and Super-admin is passed as true here + clusters, err := impl.clusterService.FindAllForClusterByUserId(1, true) + if err != nil { + impl.logger.Errorw("error encountered in getClusterResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithClusterResp(clusters), nil +} + +func (impl *UserResourceServiceImpl) getClusterNamespacesResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // System User id is passed as 1 as rbac enforcement is handled globally at bottom level and Super-admin is passed as true here + namespaces, err := impl.clusterService.FindAllNamespacesByUserIdAndClusterId(bean.SystemUserId, reqBean.ClusterId, true) + if err != nil { + impl.logger.Errorw("error encountered in getClusterNamespacesResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithNameSpaces(namespaces), nil +} + +func (impl *UserResourceServiceImpl) getClusterApiResourceOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // System User id is passed as 1 as rbac enforcement is handled globally at bottom level and Super-admin is passed as true here + apiResources, err := impl.k8sApplicationService.GetAllApiResources(context, reqBean.ClusterId, true, bean.SystemUserId) + if err != nil { + impl.logger.Errorw("error encountered in getClusterApiResourceOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithApiResourcesResp(apiResources), nil +} + +func (impl *UserResourceServiceImpl) getClusterResourceListOptions(context context.Context, token string, + reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean5.ResourceOptionsDto, error) { + // rbac enforcement is handled globally at bottom level and rbac function is passed as true + clusterRbacFunc := func(token, clusterName string, request bean4.ResourceRequestBean, casbinAction string) bool { + return true + } + resourceList, err := impl.k8sApplicationService.GetResourceList(context, token, reqBean.ResourceRequestBean, clusterRbacFunc) + if err != nil { + impl.logger.Errorw("error in getClusterResourceListOptions", "err", err) + return nil, err + } + return bean5.NewResourceOptionsDto().WithClusterResourcesResp(resourceList), nil +} diff --git a/pkg/userResource/adapter/adapter.go b/pkg/userResource/adapter/adapter.go new file mode 100644 index 0000000000..874b61b8f2 --- /dev/null +++ b/pkg/userResource/adapter/adapter.go @@ -0,0 +1,28 @@ +package adapter + +import ( + bean2 "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/internal/sql/repository/helper" + "github.com/devtron-labs/devtron/pkg/app" + "github.com/devtron-labs/devtron/pkg/userResource/bean" +) + +func BuildUserResourceResponseDto(data interface{}) *bean.UserResourceResponseDto { + return &bean.UserResourceResponseDto{ + Data: data, + } +} + +func BuildNullDataUserResourceResponseDto() *bean.UserResourceResponseDto { + return &bean.UserResourceResponseDto{ + Data: nil, + } +} + +func BuildFetchAppListingReqForJobFromDto(reqBean *bean2.ResourceOptionsReqDto) app.FetchAppListingRequest { + return app.FetchAppListingRequest{ + Teams: reqBean.TeamIds, + SortBy: helper.AppNameSortBy, // default values set + SortOrder: helper.Asc, // default values set + } +} diff --git a/pkg/userResource/bean/bean.go b/pkg/userResource/bean/bean.go new file mode 100644 index 0000000000..03f07bd071 --- /dev/null +++ b/pkg/userResource/bean/bean.go @@ -0,0 +1,102 @@ +package bean + +import ( + "github.com/devtron-labs/common-lib/utils/k8s" + "github.com/devtron-labs/devtron/api/bean/AppView" + "github.com/devtron-labs/devtron/pkg/app" + "github.com/devtron-labs/devtron/pkg/appStore/chartGroup" + bean4 "github.com/devtron-labs/devtron/pkg/appWorkflow/bean" + bean3 "github.com/devtron-labs/devtron/pkg/cluster/bean" + "github.com/devtron-labs/devtron/pkg/cluster/environment/bean" + bean2 "github.com/devtron-labs/devtron/pkg/team/bean" +) + +type UserResourceResponseDto struct { + Data interface{} `json:"data"` +} +type ResourceOptionsDto struct { + TeamsResp []bean2.TeamRequest + HelmEnvResp []*bean.ClusterEnvDto + ClusterResp []bean3.ClusterBean + NameSpaces []string + ApiResourcesResp *k8s.GetAllApiResourcesResponse + ClusterResourcesResp *k8s.ClusterResourceListMap + TeamAppResp []*app.TeamAppBean + EnvResp []bean.EnvironmentBean + ChartGroupResp *chartGroup.ChartGroupList + JobsResp []*AppView.JobContainer + AppWfsResp *bean4.WorkflowNamesResponse +} + +func NewResourceOptionsDto() *ResourceOptionsDto { + return &ResourceOptionsDto{} +} +func (r *ResourceOptionsDto) WithTeamsResp(teamsResp []bean2.TeamRequest) *ResourceOptionsDto { + r.TeamsResp = teamsResp + return r +} +func (r *ResourceOptionsDto) WithHelmEnvResp(helmEnvResp []*bean.ClusterEnvDto) *ResourceOptionsDto { + r.HelmEnvResp = helmEnvResp + return r +} +func (r *ResourceOptionsDto) WithClusterResp(clusterResp []bean3.ClusterBean) *ResourceOptionsDto { + r.ClusterResp = clusterResp + return r +} +func (r *ResourceOptionsDto) WithNameSpaces(nameSpaces []string) *ResourceOptionsDto { + r.NameSpaces = nameSpaces + return r +} +func (r *ResourceOptionsDto) WithApiResourcesResp(apiResourcesResp *k8s.GetAllApiResourcesResponse) *ResourceOptionsDto { + r.ApiResourcesResp = apiResourcesResp + return r + +} +func (r *ResourceOptionsDto) WithClusterResourcesResp(clusterResourcesResp *k8s.ClusterResourceListMap) *ResourceOptionsDto { + r.ClusterResourcesResp = clusterResourcesResp + return r +} +func (r *ResourceOptionsDto) WithTeamAppResp(teamAppResp []*app.TeamAppBean) *ResourceOptionsDto { + r.TeamAppResp = teamAppResp + return r +} + +func (r *ResourceOptionsDto) WithEnvResp(envResp []bean.EnvironmentBean) *ResourceOptionsDto { + r.EnvResp = envResp + return r +} +func (r *ResourceOptionsDto) WithChartGroupResp(chartGroupResp *chartGroup.ChartGroupList) *ResourceOptionsDto { + r.ChartGroupResp = chartGroupResp + return r +} +func (r *ResourceOptionsDto) WithJobsResp(jobsResp []*AppView.JobContainer) *ResourceOptionsDto { + r.JobsResp = jobsResp + return r +} +func (r *ResourceOptionsDto) WithAppWfsResp(appWfsResp *bean4.WorkflowNamesResponse) *ResourceOptionsDto { + r.AppWfsResp = appWfsResp + return r +} + +type Version string +type UserResourceKind string + +const ( + KindTeam UserResourceKind = "team" + KindEnvironment UserResourceKind = "environment" + Application UserResourceKind = "application" + KindDevtronApplication UserResourceKind = Application + "/devtron-application" + KindHelmApplication UserResourceKind = Application + "/helm-application" + KindHelmEnvironment UserResourceKind = "environment/helm" + KindCluster UserResourceKind = "cluster" + KindChartGroup UserResourceKind = "chartGroup" + KindJobs UserResourceKind = "jobs" + KindWorkflow UserResourceKind = "workflow" + ClusterNamespaces UserResourceKind = "cluster/namespaces" + ClusterApiResources UserResourceKind = "cluster/apiResources" + ClusterResources UserResourceKind = "cluster/resources" +) + +const ( + Alpha1Version Version = "alpha1" +) diff --git a/pkg/userResource/bean/messages.go b/pkg/userResource/bean/messages.go new file mode 100644 index 0000000000..b71c8c199e --- /dev/null +++ b/pkg/userResource/bean/messages.go @@ -0,0 +1,15 @@ +package bean + +const ( + InvalidPayloadMessage = "Invalid Payload" + InvalidEntityMessage = "Invalid Entity" +) + +// messages +const ( + RequestInvalidKindVersionErrMessage = "Invalid kind and version! Implementation not supported." +) + +const ( + UnAuthorizedAccess = "unauthorized" +) diff --git a/pkg/userResource/helper/helper.go b/pkg/userResource/helper/helper.go new file mode 100644 index 0000000000..58251e3c91 --- /dev/null +++ b/pkg/userResource/helper/helper.go @@ -0,0 +1,18 @@ +package helper + +import ( + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + "github.com/devtron-labs/devtron/internal/util" + bean5 "github.com/devtron-labs/devtron/pkg/userResource/bean" + "net/http" +) + +func ValidateResourceOptionReqBean(reqBean *apiBean.ResourceOptionsReqDto) error { + if reqBean == nil { + return util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.InvalidPayloadMessage, bean5.InvalidPayloadMessage) + } + if len(reqBean.EntityAccessType.Entity) == 0 { + return util.GetApiErrorAdapter(http.StatusBadRequest, "400", bean5.InvalidEntityMessage, bean5.InvalidEntityMessage) + } + return nil +} diff --git a/pkg/userResource/logicRouteService.go b/pkg/userResource/logicRouteService.go new file mode 100644 index 0000000000..3fedb1eae5 --- /dev/null +++ b/pkg/userResource/logicRouteService.go @@ -0,0 +1,97 @@ +package userResource + +import ( + "context" + "fmt" + apiBean "github.com/devtron-labs/devtron/api/userResource/bean" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" + "github.com/devtron-labs/devtron/pkg/userResource/bean" +) + +func getUserResourceKindWithEntityAccessKey(kind bean.UserResourceKind, version bean.Version, entity, accessType string) string { + return fmt.Sprintf("%s_%s_%s", getUserResourceKindWithVersionKey(kind, version), entity, accessType) +} +func getUserResourceKindWithVersionKey(kind bean.UserResourceKind, version bean.Version) string { + return fmt.Sprintf("%s_%s", kind, version) +} + +var mapOfUserResourceKindToAllResourceOptionsFunc = map[string]func(impl *UserResourceServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error){ + getUserResourceKindWithVersionKey(bean.KindTeam, bean.Alpha1Version): (*UserResourceServiceImpl).getTeamResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceServiceImpl).getHelmEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmApplication, bean.Alpha1Version): (*UserResourceServiceImpl).getHelmAppResourceOptions, + getUserResourceKindWithVersionKey(bean.KindCluster, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterApiResources, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterApiResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterNamespaces, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterNamespacesResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterResources, bean.Alpha1Version): (*UserResourceServiceImpl).getClusterResourceListOptions, +} + +func getAllResourceOptionsFunc(kind bean.UserResourceKind, version bean.Version) func(impl *UserResourceServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error) { + if f, ok := mapOfUserResourceKindToAllResourceOptionsFunc[getUserResourceKindWithVersionKey(kind, version)]; ok { + return f + } + return nil +} + +var mapOfUserResourceKindToAllResourceOptionsExtendedFunc = map[string]func(impl *UserResourceExtendedServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error){ + getUserResourceKindWithVersionKey(bean.KindTeam, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getTeamResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getHelmEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindHelmApplication, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getHelmAppResourceOptions, + getUserResourceKindWithVersionKey(bean.KindCluster, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterApiResources, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterApiResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterNamespaces, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterNamespacesResourceOptions, + getUserResourceKindWithVersionKey(bean.ClusterResources, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getClusterResourceListOptions, + getUserResourceKindWithVersionKey(bean.KindEnvironment, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getEnvResourceOptions, + getUserResourceKindWithVersionKey(bean.KindDevtronApplication, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getAppResourceOptions, + getUserResourceKindWithVersionKey(bean.KindChartGroup, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getChartGroupResourceOptions, + getUserResourceKindWithVersionKey(bean.KindJobs, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getJobsResourceOptions, + getUserResourceKindWithVersionKey(bean.KindWorkflow, bean.Alpha1Version): (*UserResourceExtendedServiceImpl).getAppWfsResourceOptions, +} + +func getAllResourceOptionsExtendedFunc(kind bean.UserResourceKind, version bean.Version) func(impl *UserResourceExtendedServiceImpl, context context.Context, token string, reqBean *apiBean.ResourceOptionsReqDto, params *apiBean.PathParams) (*bean.ResourceOptionsDto, error) { + if f, ok := mapOfUserResourceKindToAllResourceOptionsExtendedFunc[getUserResourceKindWithVersionKey(kind, version)]; ok { + return f + } + return nil +} + +var mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacFunc = map[string]func(impl *UserResourceServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error){ + getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForTeamForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindHelmApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.DEVTRON_APP): (*UserResourceServiceImpl).enforceRbacForHelmAppsListing, + getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceServiceImpl).enforceRbacForEnvForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindCluster, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterList, + getUserResourceKindWithEntityAccessKey(bean.ClusterApiResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterApiResource, + getUserResourceKindWithEntityAccessKey(bean.ClusterNamespaces, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterNamespaces, + getUserResourceKindWithEntityAccessKey(bean.ClusterResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceServiceImpl).enforceRbacForClusterResourceList, +} + +func getResourceOptionRbacFunc(kind bean.UserResourceKind, version bean.Version, entity string, accessType string) func(impl *UserResourceServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + if f, ok := mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacFunc[getUserResourceKindWithEntityAccessKey(kind, version, entity, accessType)]; ok { + return f + } + return nil +} + +var mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacExtendedFunc = map[string]func(impl *UserResourceExtendedServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error){ + getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForTeamForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindHelmApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForHelmAppsListing, + getUserResourceKindWithEntityAccessKey(bean.KindHelmEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.APP_ACCESS_TYPE_HELM): (*UserResourceExtendedServiceImpl).enforceRbacForEnvForHelmApp, + getUserResourceKindWithEntityAccessKey(bean.KindCluster, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterList, + getUserResourceKindWithEntityAccessKey(bean.ClusterApiResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterApiResource, + getUserResourceKindWithEntityAccessKey(bean.ClusterNamespaces, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterNamespaces, + getUserResourceKindWithEntityAccessKey(bean.ClusterResources, bean.Alpha1Version, bean2.CLUSTER_ENTITIY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForClusterResourceList, + getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.DEVTRON_APP): (*UserResourceExtendedServiceImpl).enforceRbacForTeamForDevtronApp, + getUserResourceKindWithEntityAccessKey(bean.KindTeam, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForTeamForJobs, + getUserResourceKindWithEntityAccessKey(bean.KindEnvironment, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.DEVTRON_APP): (*UserResourceExtendedServiceImpl).enforceRbacForEnvForDevtronApp, + getUserResourceKindWithEntityAccessKey(bean.KindEnvironment, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForEnvForJobs, + getUserResourceKindWithEntityAccessKey(bean.KindDevtronApplication, bean.Alpha1Version, bean2.ENTITY_APPS, bean2.DEVTRON_APP): (*UserResourceExtendedServiceImpl).enforceRbacForDevtronApps, + getUserResourceKindWithEntityAccessKey(bean.KindChartGroup, bean.Alpha1Version, bean2.CHART_GROUP_ENTITY, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForChartGroup, + getUserResourceKindWithEntityAccessKey(bean.KindJobs, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForJobs, + getUserResourceKindWithEntityAccessKey(bean.KindWorkflow, bean.Alpha1Version, bean2.EntityJobs, bean2.EmptyAccessType): (*UserResourceExtendedServiceImpl).enforceRbacForJobsWfs, +} + +func getResourceOptionRbacExtendedFunc(kind bean.UserResourceKind, version bean.Version, entity string, accessType string) func(impl *UserResourceExtendedServiceImpl, token string, params *apiBean.PathParams, resourceOptions *bean.ResourceOptionsDto) (*bean.UserResourceResponseDto, error) { + if f, ok := mapOfKindWithEntityAccessTypeKeyToResourceOptionRbacExtendedFunc[getUserResourceKindWithEntityAccessKey(kind, version, entity, accessType)]; ok { + return f + } + return nil +} diff --git a/scripts/casbin/11_access_manager.down.sql b/scripts/casbin/11_access_manager.down.sql new file mode 100644 index 0000000000..75051179bc --- /dev/null +++ b/scripts/casbin/11_access_manager.down.sql @@ -0,0 +1,4 @@ +BEGIN; +DELETE FROM casbin_rule where v0='role:all-access-manager___' and v1='user/*/*'; +DELETE FROM casbin_rule where v0='role:super-admin___' and v1='user/*/*'; +COMMIT; \ No newline at end of file diff --git a/scripts/casbin/11_access_manager.up.sql b/scripts/casbin/11_access_manager.up.sql new file mode 100644 index 0000000000..7dc4f74997 --- /dev/null +++ b/scripts/casbin/11_access_manager.up.sql @@ -0,0 +1,29 @@ +BEGIN; + +INSERT INTO "public"."casbin_rule" ("p_type", "v0", "v1", "v2", "v3", "v4", "v5") +SELECT 'p', 'role:all-access-manager___', 'user/*/*', '*', '*/*/*/*/*', 'allow', '' + WHERE NOT EXISTS ( + SELECT 1 FROM "public"."casbin_rule" + WHERE p_type = 'p' + AND v0 = 'role:all-access-manager___' + AND v1 = 'user/*/*' + AND v2 = '*' + AND v3 = '*/*/*/*/*' + AND v4 = 'allow' + AND v5 = '' +); + +INSERT INTO "public"."casbin_rule" ("p_type", "v0", "v1", "v2", "v3", "v4", "v5") +SELECT 'p', 'role:super-admin___', 'user/*/*', '*', '*/*/*/*/*', 'allow', '' + WHERE NOT EXISTS ( + SELECT 1 FROM "public"."casbin_rule" + WHERE p_type = 'p' + AND v0 = 'role:super-admin___' + AND v1 = 'user/*/*' + AND v2 = '*' + AND v3 = '*/*/*/*/*' + AND v4 = 'allow' + AND v5 = '' +); + +COMMIT; \ No newline at end of file diff --git a/scripts/sql/32303100_access_manager.down.sql b/scripts/sql/32303100_access_manager.down.sql new file mode 100644 index 0000000000..c743683d2e --- /dev/null +++ b/scripts/sql/32303100_access_manager.down.sql @@ -0,0 +1,10 @@ +BEGIN; +ALTER TABLE rbac_role_resource_detail DROP COLUMN IF EXISTS "role_resource_version"; + +DELETE FROM rbac_policy_resource_detail where resource ='user/entity/accessType'; +DELETE FROM rbac_role_resource_detail where resource in ('action','subAction'); +DELETE FROM default_rbac_role_data where role = 'accessManager'; + +ALTER TABLE roles DROP COLUMN IF EXISTS "subaction"; + +COMMIT; \ No newline at end of file diff --git a/scripts/sql/32303100_access_manager.up.sql b/scripts/sql/32303100_access_manager.up.sql new file mode 100644 index 0000000000..3ea03d7db8 --- /dev/null +++ b/scripts/sql/32303100_access_manager.up.sql @@ -0,0 +1,72 @@ +-- BEGIN +BEGIN; + +ALTER TABLE roles ADD COLUMN IF NOT EXISTS "subaction" VARCHAR(100); + +INSERT INTO rbac_policy_resource_detail ("resource", "policy_resource_value", "allowed_actions", + "resource_object", "eligible_entity_access_types", "deleted", "created_on", + "created_by", "updated_on", "updated_by") +VALUES ('user/entity/accessType', '{ + "value": "user/%/%", + "indexKeyMap": + { + "5": "Entity", + "7": "AccessType" + } + }', ARRAY['get','update','create','delete','patch'],'{ + "value": "%/%/%/%/%", + "indexKeyMap": + { + "0": "TeamObj", + "2": "EnvObj", + "4": "AppObj", + "6": "Action", + "8": "SubAction" + } + }', ARRAY['apps/devtron-app','apps/helm-app','cluster','jobs','release','chart-group'],'f','now()', 1, 'now()', 1); + +ALTER TABLE rbac_role_resource_detail ADD COLUMN IF NOT EXISTS "role_resource_version" VARCHAR(250)[] DEFAULT ARRAY['base']::VARCHAR[]; + + +INSERT INTO rbac_role_resource_detail ("resource", "role_resource_key", "role_resource_update_key", + "eligible_entity_access_types","role_resource_version", "deleted", "created_on", "created_by", + "updated_on", "updated_by") +VALUES ('subAction', 'SubAction', 'SubAction', ARRAY['apps/devtron-app','apps/helm-app','cluster','jobs','release','chart-group'], ARRAY['v1'],false, now(), 1, now(), 1); + +INSERT into default_rbac_role_data (role, + default_role_data, + created_on, + created_by, + updated_on, + updated_by, + enabled) +VALUES ('accessManager', + '{ + "entity": "apps", + "roleName": "accessManager", + "accessType": "devtron-app", + "roleDescription": "Can manage access of users with specific roles", + "roleDisplayName": "Access Manager", + "policyResourceList": + [ + { + "actions": + [ + "*" + ], + "resource": "user/entity/accessType" + } + ], + "updatePoliciesForExistingProvidedRoles": false, + "roleResourceVersions": ["v1"] +}', + now(), + 1, + now(), + 1, + true); + +-- UPDATING ONLY DESCRIPTION FOR DEFAULT ROLES +UPDATE default_rbac_role_data set default_role_data = jsonb_set(default_role_data, '{roleDescription}', '"Can approve artifact promotion requests for the selected applications and environment combinations"', true) where role = 'artifactPromoter'; +UPDATE default_rbac_role_data set default_role_data = jsonb_set(default_role_data, '{roleDescription}', '"Can approve configuration change requests for the selected applications and environment combinations"', true) where role = 'configApprover'; +COMMIT ; \ No newline at end of file diff --git a/specs/userResource/userResource.yaml b/specs/userResource/userResource.yaml new file mode 100644 index 0000000000..3fdc67128e --- /dev/null +++ b/specs/userResource/userResource.yaml @@ -0,0 +1,148 @@ +openapi: 3.0.0 +info: + title: OPI Orchestrator API + description: API for fetching user resource options based on kind and version. + version: 1.0.0 + +paths: + /orchestrator/user/resource/options/{kind}/{version}: + post: + summary: Get user resource options + description: Returns options based on the kind and version of the user resource. + parameters: + - name: kind + in: path + required: true + schema: + type: string + enum: + - team + - environment + - application/devtron-application + - application/helm-application + - environment/helm + - cluster + - chartGroup + - jobs + - workflow + - cluster/namespaces + - cluster/apiResources + - cluster/resources + description: Type of user resource. + - name: version + in: path + required: true + schema: + type: string + enum: + - alpha1 + description: API version. + + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - entity + properties: + entity: + type: string + accessType: + type: string + teamIds: + type: array + items: + type: integer + appNames: + type: array + items: + type: string + clusterId: + type: integer + k8sRequest: + type: object + properties: + resourceIdentifier: + type: object + properties: + groupVersionKind: + type: object + properties: + Group: + type: string + example: "generators.external-secrets.io" + Version: + type: string + example: "v1alpha1" + Kind: + type: string + example: "ACRAccessToken" + namespace: + type: string + example: "" + + + + responses: + '200': + description: Returns Resource Options as response + content: + application/json: + schema: + properties: + code: + type: integer + result: + $ref: '#/components/schemas/ResourceOptions' + default: + description: unexpected error + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + + +components: + schemas: + ResourceOptions: + required: + - data + properties: + data: + type: object + description: generic data field which will contain relevant data + ErrorResponse: + required: + - code + - status + properties: + code: + type: integer + format: int32 + description: Error code + status: + type: string + description: Error message + errors: + type: array + description: errors + items: + $ref: '#/components/schemas/Error' + + Error: + type: object + required: + - code + - status + properties: + code: + type: string + description: Error internal code + internalMessage: + type: string + description: Error internal message + userMessage: + type: string + description: Error user message \ No newline at end of file diff --git a/util/commonEnforcementFunctionsUtil/RbacEnforcementUtil.go b/util/commonEnforcementFunctionsUtil/RbacEnforcementUtil.go new file mode 100644 index 0000000000..019d7ba9b7 --- /dev/null +++ b/util/commonEnforcementFunctionsUtil/RbacEnforcementUtil.go @@ -0,0 +1,102 @@ +package commonEnforcementFunctionsUtil + +import ( + "github.com/devtron-labs/devtron/pkg/app" + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + "github.com/devtron-labs/devtron/pkg/auth/user" + bean2 "github.com/devtron-labs/devtron/pkg/cluster/environment/bean" + "github.com/devtron-labs/devtron/util/rbac" + "go.uber.org/zap" + "strings" +) + +type CommonEnforcementUtil interface { + CheckAuthorizationForGlobalEnvironment(token string, object string) bool + CheckAuthorizationByEmailInBatchForGlobalEnvironment(token string, object []string) map[string]bool + CheckAuthorisationForEnvs(token string, environments []bean2.EnvironmentBean) []bean2.EnvironmentBean + CheckAuthorisationOnApp(token string, projectWiseApps []*app.TeamAppBean) []*app.TeamAppBean + CheckRbacForMangerAndAboveAccess(token string, userId int32) (bool, error) +} +type CommonEnforcementUtilImpl struct { + enforcer casbin.Enforcer + enforcerUtil rbac.EnforcerUtil + logger *zap.SugaredLogger + userService user.UserService + userCommonService user.UserCommonService +} + +func NewCommonEnforcementUtilImpl(enforcer casbin.Enforcer, + enforcerUtil rbac.EnforcerUtil, + logger *zap.SugaredLogger, + userService user.UserService, + userCommonService user.UserCommonService) *CommonEnforcementUtilImpl { + return &CommonEnforcementUtilImpl{ + enforcer: enforcer, + enforcerUtil: enforcerUtil, + logger: logger, + userService: userService, + userCommonService: userCommonService, + } +} + +func (impl *CommonEnforcementUtilImpl) CheckAuthorizationForGlobalEnvironment(token string, object string) bool { + if ok := impl.enforcer.Enforce(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, object); !ok { + return false + } + return true +} + +func (impl *CommonEnforcementUtilImpl) CheckAuthorizationByEmailInBatchForGlobalEnvironment(token string, object []string) map[string]bool { + var objectResult map[string]bool + if len(object) > 0 { + objectResult = impl.enforcer.EnforceInBatch(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, object) + } + return objectResult +} + +func (impl *CommonEnforcementUtilImpl) CheckAuthorisationForEnvs(token string, environments []bean2.EnvironmentBean) []bean2.EnvironmentBean { + grantedEnvironment := make([]bean2.EnvironmentBean, 0, len(environments)) + // RBAC enforcer applying + var envIdentifierList []string + for _, item := range environments { + envIdentifierList = append(envIdentifierList, strings.ToLower(item.EnvironmentIdentifier)) + } + + result := impl.enforcer.EnforceInBatch(token, casbin.ResourceGlobalEnvironment, casbin.ActionGet, envIdentifierList) + for _, item := range environments { + + var hasAccess bool + EnvironmentIdentifier := item.ClusterName + "__" + item.Namespace + if item.EnvironmentIdentifier != EnvironmentIdentifier { + // fix for futuristic case + hasAccess = result[strings.ToLower(EnvironmentIdentifier)] || result[strings.ToLower(item.EnvironmentIdentifier)] + } else { + hasAccess = result[strings.ToLower(item.EnvironmentIdentifier)] + } + if hasAccess { + grantedEnvironment = append(grantedEnvironment, item) + } + } + return grantedEnvironment +} +func (impl *CommonEnforcementUtilImpl) CheckAuthorisationOnApp(token string, projectWiseApps []*app.TeamAppBean) []*app.TeamAppBean { + isActionUserSuperAdmin := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + for _, project := range projectWiseApps { + var accessedApps []*app.AppBean + for _, app := range project.AppList { + if isActionUserSuperAdmin { + accessedApps = append(accessedApps, app) + continue + } + object := impl.enforcerUtil.GetAppRBACNameByAppAndProjectName(project.ProjectName, app.Name) + if ok := impl.enforcer.Enforce(token, casbin.ResourceApplications, casbin.ActionGet, object); ok { + accessedApps = append(accessedApps, app) + } + } + if len(accessedApps) == 0 { + accessedApps = make([]*app.AppBean, 0) + } + project.AppList = accessedApps + } + return projectWiseApps +} diff --git a/util/commonEnforcementFunctionsUtil/UserRbacEnforcementUtil.go b/util/commonEnforcementFunctionsUtil/UserRbacEnforcementUtil.go new file mode 100644 index 0000000000..0a8b9689a7 --- /dev/null +++ b/util/commonEnforcementFunctionsUtil/UserRbacEnforcementUtil.go @@ -0,0 +1,56 @@ +package commonEnforcementFunctionsUtil + +import ( + "github.com/devtron-labs/devtron/pkg/auth/authorisation/casbin" + bean2 "github.com/devtron-labs/devtron/pkg/auth/user/bean" +) + +func (impl *CommonEnforcementUtilImpl) CheckRbacForMangerAndAboveAccess(token string, userId int32) (bool, error) { + isAuthorised := impl.enforcer.Enforce(token, casbin.ResourceGlobal, casbin.ActionGet, "*") + if !isAuthorised { + user, err := impl.userService.GetByIdWithoutGroupClaims(userId) + if err != nil { + impl.logger.Errorw("error in getting user by id", "err", err) + return false, err + } + var roleFilters []bean2.RoleFilter + if len(user.UserRoleGroup) > 0 { + groupRoleFilters, err := impl.userService.GetRoleFiltersByUserRoleGroups(user.UserRoleGroup) + if err != nil { + impl.logger.Errorw("Error in getting role filters by group names", "err", err, "UserRoleGroup", user.UserRoleGroup) + return false, err + } + if len(groupRoleFilters) > 0 { + roleFilters = append(roleFilters, groupRoleFilters...) + } + } + if user.RoleFilters != nil && len(user.RoleFilters) > 0 { + roleFilters = append(roleFilters, user.RoleFilters...) + } + if len(roleFilters) > 0 { + for _, filter := range roleFilters { + if len(filter.Team) > 0 { + if ok := impl.enforcer.Enforce(token, casbin.ResourceUser, casbin.ActionGet, filter.Team); ok { + isAuthorised = true + break + } + } + if filter.Entity == bean2.CLUSTER_ENTITIY { + if ok := impl.userCommonService.CheckRbacForClusterEntity(filter.Cluster, filter.Namespace, filter.Group, filter.Kind, filter.Resource, token, impl.checkManagerAuth); ok { + isAuthorised = true + break + } + } + } + } + } + return isAuthorised, nil +} + +func (impl *CommonEnforcementUtilImpl) checkManagerAuth(resource, token string, object string) bool { + if ok := impl.enforcer.Enforce(token, resource, casbin.ActionUpdate, object); !ok { + return false + } + return true + +} diff --git a/wire_gen.go b/wire_gen.go index 0ed70cf982..6a846a465a 100644 --- a/wire_gen.go +++ b/wire_gen.go @@ -1,6 +1,6 @@ // Code generated by Wire. DO NOT EDIT. -//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:generate go run github.com/google/wire/cmd/wire //go:build !wireinject // +build !wireinject @@ -68,6 +68,7 @@ import ( "github.com/devtron-labs/devtron/api/sse" team2 "github.com/devtron-labs/devtron/api/team" terminal2 "github.com/devtron-labs/devtron/api/terminal" + userResource2 "github.com/devtron-labs/devtron/api/userResource" util4 "github.com/devtron-labs/devtron/api/util" webhookHelm2 "github.com/devtron-labs/devtron/api/webhook/helm" "github.com/devtron-labs/devtron/cel" @@ -255,6 +256,7 @@ import ( read4 "github.com/devtron-labs/devtron/pkg/team/read" repository8 "github.com/devtron-labs/devtron/pkg/team/repository" "github.com/devtron-labs/devtron/pkg/terminal" + "github.com/devtron-labs/devtron/pkg/userResource" util3 "github.com/devtron-labs/devtron/pkg/util" "github.com/devtron-labs/devtron/pkg/variables" "github.com/devtron-labs/devtron/pkg/variables/parsers" @@ -265,6 +267,7 @@ import ( "github.com/devtron-labs/devtron/pkg/workflow/dag" status2 "github.com/devtron-labs/devtron/pkg/workflow/status" util2 "github.com/devtron-labs/devtron/util" + "github.com/devtron-labs/devtron/util/commonEnforcementFunctionsUtil" "github.com/devtron-labs/devtron/util/cron" "github.com/devtron-labs/devtron/util/rbac" ) @@ -334,7 +337,8 @@ func InitializeApp() (*App, error) { } userAuditRepositoryImpl := repository4.NewUserAuditRepositoryImpl(db) userAuditServiceImpl := user.NewUserAuditServiceImpl(sugaredLogger, userAuditRepositoryImpl) - userServiceImpl := user.NewUserServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, sessionManager, userCommonServiceImpl, userAuditServiceImpl) + roleGroupServiceImpl := user.NewRoleGroupServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, userCommonServiceImpl) + userServiceImpl := user.NewUserServiceImpl(userAuthRepositoryImpl, sugaredLogger, userRepositoryImpl, roleGroupRepositoryImpl, sessionManager, userCommonServiceImpl, userAuditServiceImpl, roleGroupServiceImpl) environmentVariables, err := util2.GetEnvironmentVariables() if err != nil { return nil, err @@ -457,17 +461,18 @@ func InitializeApp() (*App, error) { ociRegistryConfigRepositoryImpl := repository9.NewOCIRegistryConfigRepositoryImpl(db) dockerRegistryConfigImpl := pipeline.NewDockerRegistryConfigImpl(sugaredLogger, helmAppServiceImpl, dockerArtifactStoreRepositoryImpl, dockerRegistryIpsConfigRepositoryImpl, ociRegistryConfigRepositoryImpl, argoClientWrapperServiceImpl) deleteServiceExtendedImpl := delete2.NewDeleteServiceExtendedImpl(sugaredLogger, teamServiceImpl, clusterServiceImplExtended, environmentServiceImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, chartRepositoryServiceImpl, installedAppRepositoryImpl, dockerRegistryConfigImpl, dockerArtifactStoreRepositoryImpl, k8sServiceImpl, k8sInformerFactoryImpl) - environmentRestHandlerImpl := cluster3.NewEnvironmentRestHandlerImpl(environmentServiceImpl, environmentReadServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceExtendedImpl, k8sServiceImpl, k8sCommonServiceImpl) - environmentRouterImpl := cluster3.NewEnvironmentRouterImpl(environmentRestHandlerImpl) transactionUtilImpl := sql.NewTransactionUtilImpl(db) + ciPipelineRepositoryImpl := pipelineConfig.NewCiPipelineRepositoryImpl(db, sugaredLogger, transactionUtilImpl) + enforcerUtilImpl := rbac.NewEnforcerUtilImpl(sugaredLogger, teamRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, clusterRepositoryImpl, enforcerImpl, dbMigrationServiceImpl, teamReadServiceImpl) + commonEnforcementUtilImpl := commonEnforcementFunctionsUtil.NewCommonEnforcementUtilImpl(enforcerImpl, enforcerUtilImpl, sugaredLogger, userServiceImpl, userCommonServiceImpl) + environmentRestHandlerImpl := cluster3.NewEnvironmentRestHandlerImpl(environmentServiceImpl, environmentReadServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceExtendedImpl, k8sServiceImpl, k8sCommonServiceImpl, commonEnforcementUtilImpl) + environmentRouterImpl := cluster3.NewEnvironmentRouterImpl(environmentRestHandlerImpl) genericNoteRepositoryImpl := repository10.NewGenericNoteRepositoryImpl(db, transactionUtilImpl) genericNoteHistoryRepositoryImpl := repository10.NewGenericNoteHistoryRepositoryImpl(db, transactionUtilImpl) genericNoteHistoryServiceImpl := genericNotes.NewGenericNoteHistoryServiceImpl(genericNoteHistoryRepositoryImpl, sugaredLogger) genericNoteServiceImpl := genericNotes.NewGenericNoteServiceImpl(genericNoteRepositoryImpl, genericNoteHistoryServiceImpl, userRepositoryImpl, sugaredLogger) clusterDescriptionRepositoryImpl := repository5.NewClusterDescriptionRepositoryImpl(db, sugaredLogger) clusterDescriptionServiceImpl := cluster.NewClusterDescriptionServiceImpl(clusterDescriptionRepositoryImpl, userRepositoryImpl, sugaredLogger) - ciPipelineRepositoryImpl := pipelineConfig.NewCiPipelineRepositoryImpl(db, sugaredLogger, transactionUtilImpl) - enforcerUtilImpl := rbac.NewEnforcerUtilImpl(sugaredLogger, teamRepositoryImpl, appRepositoryImpl, environmentRepositoryImpl, pipelineRepositoryImpl, ciPipelineRepositoryImpl, clusterRepositoryImpl, enforcerImpl, dbMigrationServiceImpl, teamReadServiceImpl) clusterRbacServiceImpl := rbac2.NewClusterRbacServiceImpl(environmentServiceImpl, enforcerImpl, enforcerUtilImpl, clusterServiceImplExtended, sugaredLogger, userServiceImpl, clusterReadServiceImpl) clusterRestHandlerImpl := cluster3.NewClusterRestHandlerImpl(clusterServiceImplExtended, genericNoteServiceImpl, clusterDescriptionServiceImpl, sugaredLogger, userServiceImpl, validate, enforcerImpl, deleteServiceExtendedImpl, environmentServiceImpl, clusterRbacServiceImpl) clusterRouterImpl := cluster3.NewClusterRouterImpl(clusterRestHandlerImpl) @@ -479,7 +484,7 @@ func InitializeApp() (*App, error) { envConfigOverrideRepositoryImpl := chartConfig.NewEnvConfigOverrideRepository(db) gitProviderRepositoryImpl := repository11.NewGitProviderRepositoryImpl(db) gitProviderReadServiceImpl := read7.NewGitProviderReadService(sugaredLogger, gitProviderRepositoryImpl) - envConfigOverrideReadServiceImpl := read8.NewEnvConfigOverrideReadServiceImpl(envConfigOverrideRepositoryImpl, chartRepositoryImpl, sugaredLogger) + envConfigOverrideReadServiceImpl := read8.NewEnvConfigOverrideReadServiceImpl(envConfigOverrideRepositoryImpl, sugaredLogger) commonBaseServiceImpl := commonService.NewCommonBaseServiceImpl(sugaredLogger, environmentVariables, moduleReadServiceImpl) commonServiceImpl := commonService.NewCommonServiceImpl(sugaredLogger, chartRepositoryImpl, envConfigOverrideRepositoryImpl, dockerArtifactStoreRepositoryImpl, attributesRepositoryImpl, environmentRepositoryImpl, appRepositoryImpl, gitOpsConfigReadServiceImpl, gitProviderReadServiceImpl, envConfigOverrideReadServiceImpl, commonBaseServiceImpl, teamReadServiceImpl) configMapRepositoryImpl := chartConfig.NewConfigMapRepositoryImpl(sugaredLogger, db) @@ -720,7 +725,7 @@ func InitializeApp() (*App, error) { if err != nil { return nil, err } - appDeploymentTypeChangeManagerImpl := pipeline.NewAppDeploymentTypeChangeManagerImpl(sugaredLogger, pipelineRepositoryImpl, appServiceImpl, appStatusRepositoryImpl, helmAppServiceImpl, appArtifactManagerImpl, cdPipelineConfigServiceImpl, gitOpsConfigReadServiceImpl, chartServiceImpl, workflowEventPublishServiceImpl, deploymentConfigServiceImpl, chartReadServiceImpl) + appDeploymentTypeChangeManagerImpl := pipeline.NewAppDeploymentTypeChangeManagerImpl(sugaredLogger, pipelineRepositoryImpl, appServiceImpl, appStatusRepositoryImpl, helmAppServiceImpl, appArtifactManagerImpl, cdPipelineConfigServiceImpl, gitOpsConfigReadServiceImpl, chartServiceImpl, workflowEventPublishServiceImpl, deploymentConfigServiceImpl, chartReadServiceImpl, deploymentConfigReadServiceImpl) devtronAppConfigServiceImpl := pipeline.NewDevtronAppConfigServiceImpl(sugaredLogger, ciCdPipelineOrchestratorImpl, appRepositoryImpl, pipelineRepositoryImpl, resourceGroupServiceImpl, enforcerUtilImpl, ciMaterialConfigServiceImpl) pipelineBuilderImpl := pipeline.NewPipelineBuilderImpl(sugaredLogger, gitMaterialReadServiceImpl, chartRepositoryImpl, ciPipelineConfigServiceImpl, ciMaterialConfigServiceImpl, appArtifactManagerImpl, devtronAppCMCSServiceImpl, devtronAppStrategyServiceImpl, appDeploymentTypeChangeManagerImpl, cdPipelineConfigServiceImpl, devtronAppConfigServiceImpl) deploymentTemplateValidationServiceImpl := deploymentTemplate.NewDeploymentTemplateValidationServiceImpl(sugaredLogger, chartRefServiceImpl, scopedVariableManagerImpl) @@ -800,8 +805,7 @@ func InitializeApp() (*App, error) { notificationRouterImpl := router.NewNotificationRouterImpl(notificationRestHandlerImpl) 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, commonEnforcementUtilImpl) userRouterImpl := user2.NewUserRouterImpl(userRestHandlerImpl) chartRefRestHandlerImpl := restHandler.NewChartRefRestHandlerImpl(sugaredLogger, chartRefServiceImpl, chartServiceImpl) chartRefRouterImpl := router.NewChartRefRouterImpl(chartRefRestHandlerImpl) @@ -946,7 +950,7 @@ func InitializeApp() (*App, error) { serviceImpl := resourceTree.NewServiceImpl(sugaredLogger, appListingServiceImpl, appStatusServiceImpl, argoApplicationServiceExtendedImpl, cdApplicationStatusUpdateHandlerImpl, helmAppReadServiceImpl, helmAppServiceImpl, k8sApplicationServiceImpl, k8sCommonServiceImpl, environmentReadServiceImpl) appListingRestHandlerImpl := appList.NewAppListingRestHandlerImpl(appListingServiceImpl, enforcerImpl, pipelineBuilderImpl, sugaredLogger, enforcerUtilImpl, deploymentGroupServiceImpl, userServiceImpl, k8sCommonServiceImpl, installedAppDBExtendedServiceImpl, installedAppResourceServiceImpl, pipelineRepositoryImpl, k8sApplicationServiceImpl, deploymentConfigServiceImpl, serviceImpl) appListingRouterImpl := appList2.NewAppListingRouterImpl(appListingRestHandlerImpl) - appInfoRestHandlerImpl := appInfo.NewAppInfoRestHandlerImpl(sugaredLogger, appCrudOperationServiceImpl, userServiceImpl, validate, enforcerUtilImpl, enforcerImpl, helmAppServiceImpl, enforcerUtilHelmImpl, genericNoteServiceImpl) + appInfoRestHandlerImpl := appInfo.NewAppInfoRestHandlerImpl(sugaredLogger, appCrudOperationServiceImpl, userServiceImpl, validate, enforcerUtilImpl, enforcerImpl, helmAppServiceImpl, enforcerUtilHelmImpl, genericNoteServiceImpl, commonEnforcementUtilImpl) appInfoRouterImpl := appInfo2.NewAppInfoRouterImpl(sugaredLogger, appInfoRestHandlerImpl) pipelineDeploymentConfigServiceImpl := pipeline.NewPipelineDeploymentConfigServiceImpl(sugaredLogger, chartRepositoryImpl, pipelineRepositoryImpl, pipelineConfigRepositoryImpl, configMapRepositoryImpl, scopedVariableCMCSManagerImpl, deployedAppMetricsServiceImpl, chartRefServiceImpl, configMapHistoryReadServiceImpl, envConfigOverrideReadServiceImpl) pipelineTriggerRestHandlerImpl := trigger.NewPipelineRestHandler(appServiceImpl, userServiceImpl, validate, enforcerImpl, teamServiceImpl, sugaredLogger, enforcerUtilImpl, deploymentGroupServiceImpl, pipelineDeploymentConfigServiceImpl, deployedAppServiceImpl, triggerServiceImpl, workflowEventPublishServiceImpl) @@ -1066,7 +1070,10 @@ func InitializeApp() (*App, error) { fluxApplicationRouterImpl := fluxApplication2.NewFluxApplicationRouterImpl(fluxApplicationRestHandlerImpl) scanningResultRestHandlerImpl := resourceScan.NewScanningResultRestHandlerImpl(sugaredLogger, userServiceImpl, imageScanServiceImpl, enforcerImpl, enforcerUtilImpl, validate) scanningResultRouterImpl := resourceScan.NewScanningResultRouterImpl(scanningResultRestHandlerImpl) - muxRouter := router.NewMuxRouter(sugaredLogger, environmentRouterImpl, clusterRouterImpl, webhookRouterImpl, userAuthRouterImpl, gitProviderRouterImpl, gitHostRouterImpl, dockerRegRouterImpl, notificationRouterImpl, teamRouterImpl, userRouterImpl, chartRefRouterImpl, configMapRouterImpl, appStoreRouterImpl, chartRepositoryRouterImpl, releaseMetricsRouterImpl, deploymentGroupRouterImpl, batchOperationRouterImpl, chartGroupRouterImpl, imageScanRouterImpl, policyRouterImpl, gitOpsConfigRouterImpl, dashboardRouterImpl, attributesRouterImpl, userAttributesRouterImpl, commonRouterImpl, grafanaRouterImpl, ssoLoginRouterImpl, telemetryRouterImpl, telemetryEventClientImplExtended, bulkUpdateRouterImpl, webhookListenerRouterImpl, appRouterImpl, coreAppRouterImpl, helmAppRouterImpl, k8sApplicationRouterImpl, pProfRouterImpl, deploymentConfigRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, globalPluginRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, cdApplicationStatusUpdateHandlerImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, globalCMCSRouterImpl, userTerminalAccessRouterImpl, jobRouterImpl, ciStatusUpdateCronImpl, resourceGroupingRouterImpl, rbacRoleRouterImpl, scopedVariableRouterImpl, ciTriggerCronImpl, proxyRouterImpl, deploymentConfigurationRouterImpl, infraConfigRouterImpl, argoApplicationRouterImpl, devtronResourceRouterImpl, fluxApplicationRouterImpl, scanningResultRouterImpl) + userResourceExtendedServiceImpl := userResource.NewUserResourceExtendedServiceImpl(sugaredLogger, teamServiceImpl, environmentServiceImpl, appCrudOperationServiceImpl, chartGroupServiceImpl, appListingServiceImpl, appWorkflowServiceImpl, k8sApplicationServiceImpl, clusterServiceImplExtended, commonEnforcementUtilImpl, enforcerUtilImpl, enforcerImpl) + restHandlerImpl := userResource2.NewUserResourceRestHandler(sugaredLogger, userServiceImpl, userResourceExtendedServiceImpl) + routerImpl := userResource2.NewUserResourceRouterImpl(restHandlerImpl) + muxRouter := router.NewMuxRouter(sugaredLogger, environmentRouterImpl, clusterRouterImpl, webhookRouterImpl, userAuthRouterImpl, gitProviderRouterImpl, gitHostRouterImpl, dockerRegRouterImpl, notificationRouterImpl, teamRouterImpl, userRouterImpl, chartRefRouterImpl, configMapRouterImpl, appStoreRouterImpl, chartRepositoryRouterImpl, releaseMetricsRouterImpl, deploymentGroupRouterImpl, batchOperationRouterImpl, chartGroupRouterImpl, imageScanRouterImpl, policyRouterImpl, gitOpsConfigRouterImpl, dashboardRouterImpl, attributesRouterImpl, userAttributesRouterImpl, commonRouterImpl, grafanaRouterImpl, ssoLoginRouterImpl, telemetryRouterImpl, telemetryEventClientImplExtended, bulkUpdateRouterImpl, webhookListenerRouterImpl, appRouterImpl, coreAppRouterImpl, helmAppRouterImpl, k8sApplicationRouterImpl, pProfRouterImpl, deploymentConfigRouterImpl, dashboardTelemetryRouterImpl, commonDeploymentRouterImpl, externalLinkRouterImpl, globalPluginRouterImpl, moduleRouterImpl, serverRouterImpl, apiTokenRouterImpl, cdApplicationStatusUpdateHandlerImpl, k8sCapacityRouterImpl, webhookHelmRouterImpl, globalCMCSRouterImpl, userTerminalAccessRouterImpl, jobRouterImpl, ciStatusUpdateCronImpl, resourceGroupingRouterImpl, rbacRoleRouterImpl, scopedVariableRouterImpl, ciTriggerCronImpl, proxyRouterImpl, deploymentConfigurationRouterImpl, infraConfigRouterImpl, argoApplicationRouterImpl, devtronResourceRouterImpl, fluxApplicationRouterImpl, scanningResultRouterImpl, routerImpl) loggingMiddlewareImpl := util4.NewLoggingMiddlewareImpl(userServiceImpl) cdWorkflowServiceImpl := cd.NewCdWorkflowServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl) cdWorkflowRunnerReadServiceImpl := read20.NewCdWorkflowRunnerReadServiceImpl(sugaredLogger, cdWorkflowRepositoryImpl)