package controllers import ( "apiserver/common" "apiserver/models" "apiserver/services" "apiserver/store" logger "apiserver/utils" utils "apiserver/utils" "encoding/json" "errors" "fmt" "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" ) type RoleController struct{} func (rc *RoleController) GetRolesInfo(ctx *gin.Context) { logger.Debug("Enter GetRolesInfo() function from ", zap.String("client_ip", ctx.ClientIP())) var fields []string filters := make([]utils.FilterCondition, 0) sortOrders := make([]utils.SortOrder, 0) uniqueFlag := false // ---- check parameters ---- for param := range ctx.Request.URL.Query() { if !utils.ContainsField(utils.SupportedQueryParams, param) && !utils.ContainsField(utils.TypeQueryParams, param) && !utils.ContainsField(utils.UniqueQueryParams, param) { err := errors.New("invalid role info query parameters of : " + param) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } } // Get query parameters typeStr := ctx.Query("type") // type=builtin/all filterStr := ctx.Query("filter") fieldsStr := ctx.Query("fields") sortStr := ctx.Query("sort") // sort=role_name:asc pagetokenStr := ctx.Query("page_token") // page_token=Aafjakjf0023A limitStr := ctx.Query("limit") // eg: limit=100 uniqueStr := ctx.Query("unique") logger.Debug("Role query values are:", zap.String("typeStr", typeStr), zap.String("fieldsStr", fieldsStr), zap.String("filterStr", filterStr), zap.String("sortStr", sortStr), zap.String("limitStr", limitStr), zap.String("pagetokenStr", pagetokenStr)) // ---- 1. check type ---- typeValue, err := utils.ParseType(typeStr, models.RolesAvailableTypes) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, err.Error(), nil) return } logger.Debug("GetRolesInfo typeValue is ", zap.String("typeValue", typeValue)) var rqueryReply []utils.OrderMap var pagetoken string if typeValue != "builtin" { // ---- 2. check limit value ---- limit, err := utils.ParseLimit(limitStr) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, err.Error(), nil) return } // ---- 3. check filter ---- if filterStr != "" { filters = utils.ParseFilters(filterStr) if filters == nil { err := errors.New("filter " + filterStr + " has syntax error") logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } } for _, filter := range filters { if !utils.ContainsField(models.ROLE_SUPPORT_FILTERS, filter.Field) { err := errors.New("unsupported filter: " + filter.Field) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } else { filterStr = filter.Value } } // check token token := ctx.GetHeader("Authorization") submitUser, err := GetUsernameFromToken(token) if err != nil { ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // check rbac permission, err := GetRequireResourcePermission(submitUser, "rbac:view") logger.Debug("get role info for rbac:view permission:", zap.Any("permission", permission)) if permission.DomainType != models.DomainTypeAll { // only query self roles list info userSelfRoles := store.GetUserRoleNames(submitUser) // if submit_user is not specified, use query request submission user var findSelfRole bool = false var reset_filterRole string for _, filter := range filters { if filter.Field == "role_name" { filterRoles := strings.Split(filter.Value, ",") for _, filterRole := range filterRoles { filterRole = strings.TrimSpace(filterRole) if !utils.ContainsField(models.BUILTIN_ROLE_NAMES, filterRole) { if utils.ContainsField(userSelfRoles, filterRole) { findSelfRole = true reset_filterRole = filterRole + "," } } else { findSelfRole = true } } if !findSelfRole { err := errors.New("no permission") logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, err.Error(), nil) return } } } if filterStr == "" { filters = append(filters, utils.FilterCondition{ Field: "role_name", Operator: "in", Value: strings.Join(userSelfRoles, ","), }) } else { filters = append(filters, utils.FilterCondition{ Field: "role_name", Operator: "in", Value: reset_filterRole, }) } } logger.Debug("before call ConvertToNewFilter filters:", zap.Any("filters", filters)) var role models.RoleInfo filtercds, err := utils.ConvertToNewFilter(filters, &role) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } // ---- 4. check sort ---- if sortStr == "" { logger.Debug("sort is not specified, set sort=role_name:asc as default") sortStr = "role_name:asc" } sorts, err := utils.ParseSortParam(sortStr, models.ROLE_SUPPORT_SORT) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } // ---- 5. handle fields ---- if fieldsStr != "" { fields = utils.ParseFields(fieldsStr) found := false // verify the fields is in supported list supportedFields := utils.GetSupportedFields(models.RoleFields{}) logger.Debug("role supportedFields: %v", zap.Any("supportedFields", supportedFields)) for _, field := range fields { if !utils.ContainsField(supportedFields, field) { err := errors.New("unsupported query field: " + field) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } // if user not specify role_name in fields, add role_name as default field. if field == "role_name" { found = true } } if !found { logger.Debug("user doesn't specify role name in query fields, add it.") fields = append(fields, "role_name") } } else { fields = models.ROLE_DEFAULT_COLUMN } // -- 6. check unique value -- uniqueFlag = utils.ParseUnique(uniqueStr) if uniqueFlag { if fieldsStr == "" || fieldsStr != "role_name" { err := errors.New("when unique=true is specified, fields must be set to role_name") logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } limit = 0 } // ---- 7. check paging --- pagetokenStr = strings.TrimSpace(pagetokenStr) if pagetokenStr != "" { pagetokenStr, err = utils.ParsePageToken(pagetokenStr) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } } found := false for _, sortent := range sorts { if sortent.Column == "role_name" { found = true break } } if !found { sorts = append(sorts, utils.SortOrder{ Column: "role_name", Ascending: true, }) } //check all sort column is in fields err = utils.CheckAllMapKeyInList(sorts, fields) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } sortOrders = sorts queryOpts, err := utils.BuildQueryOptions(filtercds, nil, sortOrders, limit, pagetokenStr) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUERY_RESINFO_MSG+": "+err.Error(), nil) return } rqueryReply, pagetoken, err = services.QueryData(services.TableRolePermission, queryOpts, fields) if err != nil { logger.Error("Failed to query role info.", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUEUR_ROLEINFO_MSG+": "+err.Error(), nil) return } logger.Debug("the role info query reply:", zap.Any("rqueryReply", rqueryReply), zap.String("pagetoken", pagetoken)) } //convert to readable string. var queryResult utils.QueryResult if typeValue == "builtin" || typeValue == "all" { filterStr = "all" } builtinResult := processBuiltinRoles(make(map[string]utils.OrderMap), fieldsStr, filterStr) dbResult := processRoleData(rqueryReply, uniqueFlag, pagetoken) if builtinResult.Count > 0 { pagetoken = strings.TrimSpace(pagetoken) if pagetoken != "" { queryResult.Rows = dbResult.Rows queryResult.Count = dbResult.Count queryResult.Pagetoken = dbResult.Pagetoken } else { queryResult.Rows = append(builtinResult.Rows, dbResult.Rows...) queryResult.Count = builtinResult.Count + dbResult.Count queryResult.Pagetoken = dbResult.Pagetoken } } else { queryResult.Rows = dbResult.Rows queryResult.Count = dbResult.Count queryResult.Pagetoken = dbResult.Pagetoken } ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, queryResult) } func processRoleData(rows []utils.OrderMap, unique bool, pageToken string) utils.QueryResult { // Map to track seen role names for deduplication when field is specified seenRoleNames := make(map[string]bool) var processedRows []utils.OrderMap for _, row := range rows { roleName, _ := row.Data["role_name"].(string) // Deduplicate role_name when field parameter is not empty if unique { if seenRoleNames[roleName] { continue // Skip duplicate role names } seenRoleNames[roleName] = true row1 := utils.OrderMap{ Fields: []string{"role_name"}, Data: map[string]interface{}{ "role_name": roleName, }, } row.Data = row1.Data } // Process JSONBR fields (keep existing logic) for _, jsonbField := range models.JSONBRFields { if v, exist := row.Data[jsonbField]; exist { if strValue, ok := v.(string); ok { if jsonbData, err := utils.ParseJSONB(strValue); err == nil { row.Data[jsonbField] = jsonbData } else { logger.Error("Failed to parse role info JSONB field.", zap.Error(err)) } } } } processedRows = append(processedRows, row) } return utils.QueryResult{ Rows: processedRows, Count: len(processedRows), Pagetoken: pageToken, } } func processBuiltinRoles(rolePermissions map[string]utils.OrderMap, field string, filter string) utils.QueryResult { var resultRows []utils.OrderMap for _, roleName := range models.BUILTIN_ROLE_NAMES { if filter == roleName || filter == "all" { if field != "" { row1 := utils.OrderMap{ Fields: []string{"role_name"}, Data: map[string]interface{}{ "role_name": roleName, }, } resultRows = append(resultRows, row1) } else { userPerm := GetBuiltinRolePermissions(roleName) // For each permission, create a separate entry for _, perm := range userPerm { // For each domain, create a separate entry for _, domain := range perm.Domain { row := utils.OrderMap{ Fields: []string{"role_name", "action", "domain_type", "domain_values"}, Data: map[string]interface{}{ "role_name": roleName, "action": perm.Action, "domain_type": domain.Type, "domain_values": domain.Values, }, } resultRows = append(resultRows, row) } } } } } return utils.QueryResult{ Rows: resultRows, Count: len(resultRows), } } func (rc RoleController) GetRoleByRolename(ctx *gin.Context) { logger.Debug("Enter GetRoleByRolename() function from ", zap.String("client_ip", ctx.ClientIP())) // ---- check parameters ---- for param := range ctx.Request.URL.Query() { if !utils.ContainsField(utils.SupportedQueryParams, param) { err := errors.New("invalid specific role info query parameters of : " + param) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } } roleNamesStr := ctx.Param("rolename") fieldsStr := ctx.Query("fields") logger.Debug("origin query string rolename, fieldsStr values are:", zap.String("rolename", roleNamesStr), zap.String("fields", fieldsStr)) //add role_name list as filter. roleNames := strings.Split(roleNamesStr, ",") mergeRqueryReply := make([]utils.OrderMap, 0) builtinResult := make([]utils.OrderMap, 0) var page_token = "" for _, rolename := range roleNames { rolename = strings.TrimSpace(rolename) filters := make([]utils.FilterCondition, 0) sortOrders := make([]utils.SortOrder, 0) filters = append(filters, utils.FilterCondition{ Field: "role_name", Operator: "eq", Value: rolename, }) //filtercds := make([]utils.Condition, len(filters)) for _, filter := range filters { // check the filter is supported. if !utils.ContainsField(models.ROLE_SUPPORT_FILTERS, filter.Field) { err := errors.New("unsupported filter: " + filter.Field) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } } var role models.RoleInfo var err error filtercds, err := utils.ConvertToNewFilter(filters, &role) if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } var fields []string if fieldsStr != "" { fields = utils.ParseFields(fieldsStr) found := false // verify the fields is in supported list supportedFields := utils.GetSupportedFields(models.RoleInfo{}) logger.Debug("role supportedFields: %v", zap.Any("supportedFields", supportedFields)) for _, field := range fields { if !utils.ContainsField(supportedFields, field) { err := errors.New("unsupported query field: " + field) logger.Error(err.Error()) ReturnError(ctx, common.INVALID_PARAMS_CODE, common.INVALID_PARAMS_MSG+": "+err.Error(), nil) return } // if user not specify role_name in fields, add role_name as default field. if field == "role_name" { found = true } } if !found { logger.Debug("user doesn't specify role_name in query fields, add it.") fields = append(fields, "role_name") } } else { // default display columns fields = models.ROLE_DEFAULT_COLUMN } // add a default order: sort=role_name:asc sortOrders = append(sortOrders, utils.SortOrder{ Column: "role_name", Ascending: true, }) queryOpts, err := utils.BuildQueryOptions(filtercds, nil, sortOrders, utils.PER_PAGE_LIMIT, "") if err != nil { logger.Error(err.Error()) ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUEUR_ROLEINFO_MSG+": "+err.Error(), nil) return } builtinQueryResult := processBuiltinRoles(make(map[string]utils.OrderMap), fieldsStr, rolename) builtinResult = builtinQueryResult.Rows rqueryReply, page_token, err := services.QueryData(services.TableRolePermission, queryOpts, fields) if err != nil { logger.Error("Failed to query the specific role info.", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, common.ERROR_QUEUR_ROLEINFO_MSG+": "+err.Error(), nil) return } logger.Debug("specific role query reply:", zap.Any("rqueryReply", rqueryReply), zap.String("pagetoken", page_token)) mergeRqueryReply = append(mergeRqueryReply, rqueryReply...) } // end for rolename logger.Debug("the specific role query reply:", zap.Any("rqueryReply", mergeRqueryReply)) uniqueFlag := false queryResult := processRoleData(mergeRqueryReply, uniqueFlag, page_token) if len(builtinResult) > 0 { queryResult.Rows = append(builtinResult, queryResult.Rows...) queryResult.Count = len(builtinResult) + queryResult.Count } ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, queryResult) } func (rc *RoleController) AddPermissionsForRole(ctx *gin.Context) { logger.Debug("Enter AddPermissionsForRole() function from ", zap.String("client_ip", ctx.ClientIP())) roleId := ctx.Param("roleid") var req models.CreateRoleRequest req.RoleName = roleId if err := utils.BindJSONStrict(ctx, &req); err != nil { logger.Error("Invalid request parameters", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } err := preCheckRoleRequest(&req, false) if err != nil { logger.Error("Failed to create role request", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } storePerms := make([]store.RolePermission, 0, len(req.RolePermissions)) for _, perm := range req.RolePermissions { domainValuesJSON, _ := json.Marshal(perm.DomainValues) storePerms = append(storePerms, store.RolePermission{ Action: perm.Permission, DomainType: perm.DomainType, DomainValues: perm.DomainValues, }) _ = domainValuesJSON // no longer needed for store } store.AddPermissionsForRole(req.RoleName, storePerms) response := models.RoleCreateResponse{ RoleName: req.RoleName, Message: fmt.Sprintf("Role <%s> updated successfully", req.RoleName), } ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, response) return } // detele a role and ensure no user is using it, // can't delete built-in role, delete the role from role_permission table func (rc *RoleController) DeleteRoleAndPermissions(ctx *gin.Context) { logger.Debug("Enter DeleteRoleAndPermissions() function from ", zap.String("client_ip", ctx.ClientIP())) req_rolename := ctx.Param("rolename") req_rolename = strings.TrimSpace(req_rolename) logger.Debug("DeleteRoleAndPermissions() function from ", zap.String("rolename", req_rolename)) if req_rolename == "" { logger.Error("role name can't be empty", zap.String("req_rolename", req_rolename)) err := fmt.Errorf("role name can't be empty") ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // step 1: role name whether exists allRoleNames := store.GetAllRoleNames(false) found := false for _, roleName := range allRoleNames { if roleName == req_rolename { found = true break } } if !found { logger.Error("The role name not found", zap.String("req_rolename", req_rolename)) err := fmt.Errorf("role name not found: %s", req_rolename) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // step 2: built-in role if utils.ContainsField(models.BUILTIN_ROLE_NAMES, req_rolename) { logger.Error("built-in role name can't be deleted", zap.String("req_rolename", req_rolename)) err := fmt.Errorf("built-in role name <%s> can't be deleted", req_rolename) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // step 3: check whether there is a user configured with rolename allUsers := store.GetUsersUnderRoleName(req_rolename) if len(allUsers) > 0 { logger.Error("the role is still under using by the user", zap.String("req_rolename", req_rolename)) err := fmt.Errorf("the role is still under using by the user") ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // step 4: delete the role store.Roles.Delete(req_rolename) // step 6: return response logger.Debug("Role deleted successfully", zap.String("rolename", req_rolename)) Msg := fmt.Sprintf("role of <%s> deleted successfully", req_rolename) ReturnSuccess(ctx, common.SUCCESS_CODE, common.SUCCESS_MSG, Msg) return } func (rc *RoleController) CreateRole(ctx *gin.Context) { logger.Debug("Enter CreateRole() function from ", zap.String("client_ip", ctx.ClientIP())) var req models.CreateRoleRequest if err := utils.BindJSONStrict(ctx, &req); err != nil { logger.Error("Invalid request parameters", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } // precheck the request parameter err := preCheckRoleRequest(&req, true) if err != nil { logger.Error("Failed to create role request", zap.Error(err)) ReturnError(ctx, common.ERROR_CODE, err.Error(), nil) return } storePerms := make([]store.RolePermission, 0, len(req.RolePermissions)) for _, perm := range req.RolePermissions { domainValuesJSON, _ := json.Marshal(perm.DomainValues) storePerms = append(storePerms, store.RolePermission{ Action: perm.Permission, DomainType: perm.DomainType, DomainValues: perm.DomainValues, }) _ = domainValuesJSON // no longer needed for store } store.AddRoleNames(req.RoleName) store.AddPermissionsForRole(req.RoleName, storePerms) response := models.RoleCreateResponse{ RoleName: req.RoleName, Message: fmt.Sprintf("Role <%s> created successfully", req.RoleName), } ReturnSuccess(ctx, common.CREATED, common.SUCCESS_MSG, response) return } func preCheckRole(role_name string) error { return nil } func preCheckRoleRequest(req *models.CreateRoleRequest, newFlag bool) error { logger.Debug("Enter preCheckRoleRequest") if req.RoleName != "" { if newFlag { allRoleNames := store.GetAllRoleNames(true) //validate queue_name whether exists for _, roleName := range allRoleNames { if roleName == req.RoleName { logger.Error("The role name has been existed", zap.String("role_name", req.RoleName)) return fmt.Errorf("duplicated role name: %s", req.RoleName) } } } else { allRoleNames := store.GetAllRoleNames(false) found := false for _, roleName := range allRoleNames { if roleName == req.RoleName { found = true break } } if !found { logger.Error("The role name not found", zap.String("role_name", req.RoleName)) return fmt.Errorf("role name not found: %s", req.RoleName) } } } if len(req.RolePermissions) == 0 { logger.Error("Role permissions cannot be empty") return fmt.Errorf("role permissions cannot be empty") } for _, perm := range req.RolePermissions { logger.Debug("Checking permission", zap.String("permission", perm.Permission)) if !utils.ContainsField(GetAllPermissions(), perm.Permission) { logger.Error("Unsupported permission", zap.String("permission", perm.Permission)) return fmt.Errorf("unsupported permission: %s", perm.Permission) } if !newFlag { // add a new permission dbExistPerms := store.GetRoleAllPermissions(req.RoleName) if utils.ContainsField(dbExistPerms, perm.Permission) { logger.Error("The permission has been existed", zap.String("permission", perm.Permission)) return fmt.Errorf("duplicated permission: %s", perm.Permission) } } if perm.DomainType == "" { logger.Error("Domain type cannot be empty") return fmt.Errorf("domain type cannot be empty") } else { parts := strings.Split(perm.Permission, ":") objectPart := parts[0] //permission first section allowedDomainTypes := models.SupportedDomainTypes[objectPart] domainType := strings.ToLower(perm.DomainType) if !utils.ContainsField(allowedDomainTypes, domainType) { logger.Error("Unsupported domain type for permission object", zap.String("object_type", objectPart), zap.String("domain_type", domainType)) return fmt.Errorf("unsupported domain type for permission object %s: %s, and allowed types are %v", objectPart, domainType, allowedDomainTypes) } if domainType == models.DomainTypeAll || domainType == models.DomainTypeSelf || domainType == models.DomainTypeSelfHG || domainType == models.DomainTypeSelfUG { if len(perm.DomainValues) > 0 { logger.Error("Domain values should be empty for 'all' or 'self' domain type", zap.String("domain_type", domainType)) return fmt.Errorf("domain values should be empty for 'all' or 'self' domain type: %s", domainType) } } if domainType == models.DomainTypeUserGroup || domainType == models.DomainTypeHostGroup || domainType == models.DomainTypeQueue { if len(perm.DomainValues) == 0 { logger.Error("Domain values should not be empty for 'usergroups', 'hostgroups' or 'queues' domain type", zap.String("domain_type", domainType)) return fmt.Errorf("domain values should not be empty for 'usergroups', 'hostgroups' or 'queues' domain type: %s", domainType) } } switch domainType { case models.DomainTypeQueue: if !store.IsQueueExist(perm.DomainValues) { logger.Error("Queue does not exist for specified values", zap.Any("queue_name", perm.DomainValues)) return fmt.Errorf("queue does not exist for specified values: %v", perm.DomainValues) } } //end of switch } } return nil }