Skip to content

Commit

Permalink
Merge branch 'main' into ext-app-fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
prakash100198 committed May 19, 2024
2 parents 973c05d + 5a75ad9 commit cabcdc4
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 40 deletions.
25 changes: 20 additions & 5 deletions api/auth/user/UserRestHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func (handler UserRestHandlerImpl) CreateUser(w http.ResponseWriter, r *http.Req
}
//RBAC enforcer Ends

res, err := handler.userService.CreateUser(&userInfo, token, handler.CheckManagerAuth)
res, restrictedGroups, err := handler.userService.CreateUser(&userInfo, token, handler.CheckManagerAuth)
if err != nil {
handler.logger.Errorw("service err, CreateUser", "err", err, "payload", userInfo)
if _, ok := err.(*util.ApiError); ok {
Expand All @@ -203,7 +203,22 @@ func (handler UserRestHandlerImpl) CreateUser(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 len(restrictedGroups) != len(userInfo.UserRoleGroup) {
// warning
message := fmt.Errorf("User permissions added partially. %s%s", errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin)
common.WriteJsonResp(w, message, nil, http.StatusExpectationFailed)

} else {
//error
message := fmt.Errorf("Permission could not be added. %s%s", errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin)
common.WriteJsonResp(w, message, nil, http.StatusBadRequest)
}
}
}

func (handler UserRestHandlerImpl) UpdateUser(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -252,16 +267,16 @@ func (handler UserRestHandlerImpl) UpdateUser(w http.ResponseWriter, r *http.Req
if len(restrictedGroups) == 0 {
common.WriteJsonResp(w, err, res, http.StatusOK)
} else {
groups := strings.Join(restrictedGroups, ", ")
errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin := helper.CreateErrorMessageForUserRoleGroups(restrictedGroups)

if rolesChanged || groupsModified {
// warning
message := fmt.Errorf("User permissions updated partially. Group(s): " + groups + " could not be added/removed. You do not have manager permission for some or all projects in these groups.")
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: You do not have manager permission for some or all projects in group(s): " + groups + ".")
message := fmt.Errorf("Permission could not be added/removed. %s%s", errorMessageForGroupsWithoutSuperAdmin, errorMessageForGroupsWithSuperAdmin)
common.WriteJsonResp(w, message, nil, http.StatusBadRequest)
}
}
Expand Down
5 changes: 5 additions & 0 deletions api/bean/UserRequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ type RoleGroupListingResponse struct {
TotalCount int `json:"totalCount"`
}

type RestrictedGroup struct {
Group string
HasSuperAdminPermission bool
}

type ListingRequest struct {
SearchKey string `json:"searchKey"`
SortOrder bean.SortOrder `json:"sortOrder"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/apiToken/ApiTokenService.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (impl ApiTokenServiceImpl) CreateApiToken(request *openapi.CreateApiTokenRe
EmailId: email,
UserType: bean.USER_TYPE_API_TOKEN,
}
createUserResponse, err := impl.userService.CreateUser(&createUserRequest, token, managerAuth)
createUserResponse, _, err := impl.userService.CreateUser(&createUserRequest, token, managerAuth)
if err != nil {
impl.logger.Errorw("error while creating user for api-token", "email", email, "error", err)
return nil, err
Expand Down
85 changes: 51 additions & 34 deletions pkg/auth/user/UserService.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ const (
)

type UserService interface {
CreateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*bean.UserInfo, error)
CreateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*bean.UserInfo, []bean.RestrictedGroup, error)
SelfRegisterUserIfNotExists(userInfo *bean.UserInfo) ([]*bean.UserInfo, error)
UpdateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, bool, bool, []string, error)
UpdateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, bool, bool, []bean.RestrictedGroup, error)
GetById(id int32) (*bean.UserInfo, error)
GetAll() ([]bean.UserInfo, error)
GetAllWithFilters(request *bean.ListingRequest) (*bean.UserListingResponse, error)
Expand Down Expand Up @@ -276,33 +276,34 @@ func (impl *UserServiceImpl) saveUser(userInfo *bean.UserInfo, emailId string) (
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 *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) ([]*bean.UserInfo, []bean.RestrictedGroup, error) {

var pass []string
var userResponse []*bean.UserInfo
var restrictedGroups []bean.RestrictedGroup
emailIds := strings.Split(userInfo.EmailId, ",")
for _, emailId := range emailIds {
dbUser, err := impl.userRepository.FetchActiveOrDeletedUserByEmail(emailId)
if err != nil && err != pg.ErrNoRows {
impl.logger.Errorw("error while fetching user from db", "error", err)
return nil, err
return nil, nil, err
}

//if found, update it with new roles
if dbUser != nil && dbUser.Id > 0 {
userInfo, err = impl.updateUserIfExists(userInfo, dbUser, emailId, token, managerAuth)
if err != nil {
impl.logger.Errorw("error while create user if exists in db", "error", err)
return nil, err
return nil, nil, err
}
}

// if not found, create new user
if err == pg.ErrNoRows {
userInfo, err = impl.createUserIfNotExists(userInfo, emailId, token, managerAuth)
userInfo, restrictedGroups, err = impl.createUserIfNotExists(userInfo, emailId, token, managerAuth)
if err != nil {
impl.logger.Errorw("error while create user if not exists in db", "error", err)
return nil, err
return nil, nil, err
}
}

Expand All @@ -312,7 +313,7 @@ func (impl *UserServiceImpl) CreateUser(userInfo *bean.UserInfo, token string, m
userResponse = append(userResponse, &bean.UserInfo{Id: userInfo.Id, EmailId: emailId, Groups: userInfo.Groups, RoleFilters: userInfo.RoleFilters, SuperAdmin: userInfo.SuperAdmin, UserRoleGroup: userInfo.UserRoleGroup})
}

return userResponse, nil
return userResponse, restrictedGroups, nil
}

func (impl *UserServiceImpl) updateUserIfExists(userInfo *bean.UserInfo, dbUser *repository.UserModel, emailId string,
Expand Down Expand Up @@ -340,20 +341,20 @@ func (impl *UserServiceImpl) updateUserIfExists(userInfo *bean.UserInfo, dbUser
return userInfo, nil
}

func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emailId string, token string, managerAuth func(resource string, token string, object string) bool) (*bean.UserInfo, error) {
func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emailId string, token string, managerAuth func(resource string, token string, object string) bool) (*bean.UserInfo, []bean.RestrictedGroup, error) {
// if not found, create new user
dbConnection := impl.userRepository.GetConnection()
tx, err := dbConnection.Begin()
if err != nil {
return nil, err
return nil, nil, err
}
// Rollback tx on error.
defer tx.Rollback()

_, err = impl.validateUserRequest(userInfo)
if err != nil {
err = &util.ApiError{HttpStatusCode: http.StatusBadRequest, UserMessage: "Invalid request, please provide role filters"}
return nil, err
return nil, nil, err
}

//create new user in our db on d basis of info got from google api or hex. assign a basic role
Expand All @@ -375,24 +376,30 @@ func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emai
InternalMessage: "failed to create new user in db",
UserMessage: fmt.Sprintf("requested by %d", userInfo.UserId),
}
return nil, err
return nil, nil, err
}
userInfo.Id = model.Id
//loading policy for safety
casbin2.LoadPolicy()

var restrictedGroups []bean.RestrictedGroup

//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 {
isActionPerformingUserSuperAdmin, err := impl.IsSuperAdmin(int(userInfo.UserId))
if err != nil {
return nil, nil, err
}
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, token, managerAuth, tx, entity, mapping[index])
if err != nil {
impl.logger.Errorw("error in creating user roles for Alltypes", "err", err)
return nil, err
return nil, nil, err
}
policies = append(policies, policiesToBeAdded...)

Expand All @@ -402,29 +409,34 @@ func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emai
for _, item := range userInfo.UserRoleGroup {
userGroup, err := impl.roleGroupRepository.GetRoleGroupByName(item.RoleGroup.Name)
if err != nil {
return nil, err
return nil, nil, err
}
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)
}
//object := "group:" + strings.ReplaceAll(item, " ", "_")
policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(emailId), Obj: casbin2.Object(userGroup.CasbinName)})
}
// END GROUP POLICY
} else if userInfo.SuperAdmin == true {

isSuperAdmin, err := impl.IsSuperAdmin(int(userInfo.UserId))
if err != nil {
return nil, err
return nil, nil, err
}
if isSuperAdmin == false {
err = &util.ApiError{HttpStatusCode: http.StatusForbidden, UserMessage: "Invalid request, not allow to update super admin type user"}
return nil, err
return nil, nil, err
}
flag, err := impl.userAuthRepository.CreateRoleForSuperAdminIfNotExists(tx, userInfo.UserId)
if err != nil || flag == false {
return nil, err
return nil, nil, err
}
roleModel, err := impl.userAuthRepository.GetRoleByFilterForAllTypes("", "", "", "", bean2.SUPER_ADMIN, "", "", "", "", "", "", "", false, "")
if err != nil {
return nil, err
return nil, nil, err
}
if roleModel.Id > 0 {
userRoleModel := &repository.UserRoleModel{UserId: model.Id, RoleId: roleModel.Id, AuditLog: sql.AuditLog{
Expand All @@ -435,7 +447,7 @@ func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emai
}}
userRoleModel, err = impl.userAuthRepository.CreateUserRoleMapping(userRoleModel, tx)
if err != nil {
return nil, err
return nil, nil, err
}
policies = append(policies, casbin2.Policy{Type: "g", Sub: casbin2.Subject(model.EmailId), Obj: casbin2.Object(roleModel.Role)})
}
Expand All @@ -450,11 +462,11 @@ func (impl *UserServiceImpl) createUserIfNotExists(userInfo *bean.UserInfo, emai
//Ends
err = tx.Commit()
if err != nil {
return nil, err
return nil, nil, err
}
//loading policy for syncing orchestrator to casbin with newly added policies
casbin2.LoadPolicy()
return userInfo, nil
return userInfo, restrictedGroups, nil
}

func (impl *UserServiceImpl) CreateOrUpdateUserRolesForAllTypes(roleFilter bean.RoleFilter, userId int32, model *repository.UserModel, existingRoles map[int]repository.UserRoleModel, token string, managerAuth func(resource string, token string, object string) bool, tx *pg.Tx, entity string, capacity int) ([]casbin2.Policy, bool, error) {
Expand Down Expand Up @@ -634,7 +646,7 @@ func (impl UserServiceImpl) mergeUserRoleGroup(oldUserRoleGroups []bean.UserRole
return finalUserRoleGroups
}

func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, bool, bool, []string, error) {
func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, managerAuth func(resource, token string, object string) bool) (*bean.UserInfo, bool, bool, []bean.RestrictedGroup, error) {
//checking if request for same user is being processed
isLocked := impl.getUserReqLockStateById(userInfo.Id)
if isLocked {
Expand Down Expand Up @@ -698,7 +710,7 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, m
var eliminatedPolicies []casbin2.Policy
capacity, mapping := impl.userCommonService.GetCapacityForRoleFilter(userInfo.RoleFilters)
var addedPolicies = make([]casbin2.Policy, 0, capacity)
restrictedGroups := []string{}
restrictedGroups := []bean.RestrictedGroup{}
rolesChanged := false
groupsModified := false
//loading policy for safety
Expand Down Expand Up @@ -767,13 +779,13 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, m
newGroupMap[userGroup.CasbinName] = userGroup.CasbinName
if _, ok := oldGroupMap[userGroup.CasbinName]; !ok {
//check permission for new group which is going to add
hasAccessToGroup := impl.checkGroupAuth(userGroup.CasbinName, token, managerAuth, isActionPerformingUserSuperAdmin)
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 {
trimmedGroup := strings.TrimPrefix(item.RoleGroup.Name, "group:")
restrictedGroups = append(restrictedGroups, trimmedGroup)
restrictedGroup := adapter.CreateRestrictedGroup(item.RoleGroup.Name, hasSuperAdminPermission)
restrictedGroups = append(restrictedGroups, restrictedGroup)
}
}
}
Expand All @@ -783,15 +795,15 @@ func (impl *UserServiceImpl) UpdateUser(userInfo *bean.UserInfo, token string, m
if item != bean.SUPERADMIN {
//check permission for group which is going to eliminate
if strings.HasPrefix(item, "group:") {
hasAccessToGroup := impl.checkGroupAuth(item, token, managerAuth, isActionPerformingUserSuperAdmin)
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 {
trimmedGroup := strings.TrimPrefix(item, "group:")
restrictedGroups = append(restrictedGroups, trimmedGroup)
restrictedGroup := adapter.CreateRestrictedGroup(item, hasSuperAdminPermission)
restrictedGroups = append(restrictedGroups, restrictedGroup)
}
}
}
Expand Down Expand Up @@ -1672,15 +1684,20 @@ 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 {
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
return false, false
}
hasAccessToGroup := true
hasSuperAdminPermission := false
for _, role := range roles {
if role.Role == bean.SUPERADMIN && !isActionUserSuperAdmin {
hasAccessToGroup = false
hasSuperAdminPermission = true
}
if role.AccessType == bean.APP_ACCESS_TYPE_HELM && !isActionUserSuperAdmin {
hasAccessToGroup = false
}
Expand All @@ -1699,7 +1716,7 @@ func (impl *UserServiceImpl) checkGroupAuth(groupName string, token string, mana
}

}
return hasAccessToGroup
return hasAccessToGroup, hasSuperAdminPermission
}

func (impl *UserServiceImpl) GetRoleFiltersByUserRoleGroups(userRoleGroups []bean.UserRoleGroup) ([]bean.RoleFilter, error) {
Expand Down
10 changes: 10 additions & 0 deletions pkg/auth/user/adapter/adapter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package adapter

import (
"github.com/devtron-labs/devtron/api/bean"
"github.com/devtron-labs/devtron/pkg/auth/user/repository"
"strings"
"time"
)

Expand All @@ -12,3 +14,11 @@ func GetLastLoginTime(model repository.UserModel) time.Time {
}
return lastLoginTime
}

func CreateRestrictedGroup(roleGroupName string, hasSuperAdminPermission bool) bean.RestrictedGroup {
trimmedGroup := strings.TrimPrefix(roleGroupName, "group:")
return bean.RestrictedGroup{
Group: trimmedGroup,
HasSuperAdminPermission: hasSuperAdminPermission,
}
}
Loading

0 comments on commit cabcdc4

Please sign in to comment.