Skip to content

Commit 0d1bc7d

Browse files
hsato03Henrique Sato
andauthored
Limit listRoles API visibility (#8639)
Co-authored-by: Henrique Sato <henrique.sato@scclouds.com.br>
1 parent 0d8f7d4 commit 0d1bc7d

File tree

6 files changed

+227
-54
lines changed

6 files changed

+227
-54
lines changed

plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ public UserTwoFactorAuthenticationSetupResponse setupUserTwoFactorAuthentication
346346
return null;
347347
}
348348

349+
@Override
350+
public List<String> getApiNameList() {
351+
return null;
352+
}
353+
349354
@Override
350355
public boolean deleteUserAccount(long arg0) {
351356
// TODO Auto-generated method stub

server/src/main/java/com/cloud/user/AccountManager.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> s
195195
UserTwoFactorAuthenticator getUserTwoFactorAuthenticator(final Long domainId, final Long userAccountId);
196196

197197
void verifyUsingTwoFactorAuthenticationCode(String code, Long domainId, Long userAccountId);
198+
198199
UserTwoFactorAuthenticationSetupResponse setupUserTwoFactorAuthentication(SetupUserTwoFactorAuthenticationCmd cmd);
199200

201+
List<String> getApiNameList();
200202
}

server/src/main/java/com/cloud/user/AccountManagerImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@ public void setQuerySelectors(List<QuerySelector> querySelectors) {
425425
_querySelectors = querySelectors;
426426
}
427427

428+
@Override
429+
public List<String> getApiNameList() {
430+
return apiNameList;
431+
}
432+
428433
@Override
429434
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
430435
_systemAccount = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM);

server/src/main/java/org/apache/cloudstack/acl/RoleManagerImpl.java

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.HashSet;
2123
import java.util.Iterator;
2224
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Set;
2427

2528
import javax.inject.Inject;
2629

@@ -405,42 +408,100 @@ public List<Role> findRolesByName(String name) {
405408
public Pair<List<Role>, Integer> findRolesByName(String name, String keyword, Long startIndex, Long limit) {
406409
if (StringUtils.isNotBlank(name) || StringUtils.isNotBlank(keyword)) {
407410
Pair<List<RoleVO>, Integer> data = roleDao.findAllByName(name, keyword, startIndex, limit, isCallerRootAdmin());
408-
int removed = removeRootAdminRolesIfNeeded(data.first());
411+
int removed = removeRolesIfNeeded(data.first());
409412
return new Pair<List<Role>,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed));
410413
}
411414
return new Pair<List<Role>, Integer>(new ArrayList<Role>(), 0);
412415
}
413416

414417
/**
415-
* Removes roles of the given list that have the type '{@link RoleType#Admin}' if the user calling the method is not a 'root admin'.
416-
* The actual removal is executed via {@link #removeRootAdminRoles(List)}. Therefore, if the method is called by a 'root admin', we do nothing here.
418+
* Removes roles from the given list if the role has different or more permissions than the user's calling the method role
417419
*/
418-
protected int removeRootAdminRolesIfNeeded(List<? extends Role> roles) {
419-
if (!isCallerRootAdmin()) {
420-
return removeRootAdminRoles(roles);
421-
}
422-
return 0;
423-
}
424-
425-
/**
426-
* Remove all roles that have the {@link RoleType#Admin}.
427-
*/
428-
protected int removeRootAdminRoles(List<? extends Role> roles) {
429-
if (CollectionUtils.isEmpty(roles)) {
420+
protected int removeRolesIfNeeded(List<? extends Role> roles) {
421+
if (roles.isEmpty()) {
430422
return 0;
431423
}
432-
Iterator<? extends Role> rolesIterator = roles.iterator();
424+
425+
Long callerRoleId = getCurrentAccount().getRoleId();
426+
Map<String, Permission> callerRolePermissions = getRoleRulesAndPermissions(callerRoleId);
427+
433428
int count = 0;
429+
Iterator<? extends Role> rolesIterator = roles.iterator();
434430
while (rolesIterator.hasNext()) {
435431
Role role = rolesIterator.next();
436-
if (RoleType.Admin == role.getRoleType()) {
437-
count++;
438-
rolesIterator.remove();
432+
433+
if (role.getId() == callerRoleId || roleHasPermission(callerRolePermissions, role)) {
434+
continue;
439435
}
436+
437+
count++;
438+
rolesIterator.remove();
440439
}
440+
441441
return count;
442442
}
443443

444+
/**
445+
* Checks if the role of the caller account has compatible permissions of the specified role.
446+
* For each permission of the role of the caller, the target role needs to contain the same permission.
447+
*
448+
* @param sourceRolePermissions the permissions of the caller role.
449+
* @param targetRole the role that the caller role wants to access.
450+
* @return True if the role can be accessed with the given permissions; false otherwise.
451+
*/
452+
protected boolean roleHasPermission(Map<String, Permission> sourceRolePermissions, Role targetRole) {
453+
Set<String> rulesAlreadyCompared = new HashSet<>();
454+
for (RolePermission rolePermission : findAllPermissionsBy(targetRole.getId())) {
455+
boolean permissionIsRegex = rolePermission.getRule().getRuleString().contains("*");
456+
457+
for (String apiName : accountManager.getApiNameList()) {
458+
if (!rolePermission.getRule().matches(apiName) || rulesAlreadyCompared.contains(apiName)) {
459+
continue;
460+
}
461+
462+
if (rolePermission.getPermission() == Permission.ALLOW && (!sourceRolePermissions.containsKey(apiName) || sourceRolePermissions.get(apiName) == Permission.DENY)) {
463+
return false;
464+
}
465+
466+
rulesAlreadyCompared.add(apiName);
467+
468+
if (!permissionIsRegex) {
469+
break;
470+
}
471+
}
472+
}
473+
474+
return true;
475+
}
476+
477+
/**
478+
* Given a role ID, returns a {@link Map} containing the API name as the key and the {@link Permission} for the API as the value.
479+
*
480+
* @param roleId ID from role.
481+
*/
482+
public Map<String, Permission> getRoleRulesAndPermissions(Long roleId) {
483+
Map<String, Permission> roleRulesAndPermissions = new HashMap<>();
484+
485+
for (RolePermission rolePermission : findAllPermissionsBy(roleId)) {
486+
boolean permissionIsRegex = rolePermission.getRule().getRuleString().contains("*");
487+
488+
for (String apiName : accountManager.getApiNameList()) {
489+
if (!rolePermission.getRule().matches(apiName)) {
490+
continue;
491+
}
492+
493+
if (!roleRulesAndPermissions.containsKey(apiName)) {
494+
roleRulesAndPermissions.put(apiName, rolePermission.getPermission());
495+
}
496+
497+
if (!permissionIsRegex) {
498+
break;
499+
}
500+
}
501+
}
502+
return roleRulesAndPermissions;
503+
}
504+
444505
@Override
445506
public List<Role> findRolesByType(RoleType roleType) {
446507
return findRolesByType(roleType, null, null).first();
@@ -458,14 +519,14 @@ public Pair<List<Role>, Integer> findRolesByType(RoleType roleType, Long startIn
458519
@Override
459520
public List<Role> listRoles() {
460521
List<? extends Role> roles = roleDao.listAll();
461-
removeRootAdminRolesIfNeeded(roles);
522+
removeRolesIfNeeded(roles);
462523
return ListUtils.toListOfInterface(roles);
463524
}
464525

465526
@Override
466527
public Pair<List<Role>, Integer> listRoles(Long startIndex, Long limit) {
467528
Pair<List<RoleVO>, Integer> data = roleDao.listAllRoles(startIndex, limit, isCallerRootAdmin());
468-
int removed = removeRootAdminRolesIfNeeded(data.first());
529+
int removed = removeRolesIfNeeded(data.first());
469530
return new Pair<List<Role>,Integer>(ListUtils.toListOfInterface(data.first()), Integer.valueOf(data.second() - removed));
470531
}
471532

server/src/test/java/com/cloud/user/MockAccountManagerImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,4 +479,8 @@ public ConfigKey<?>[] getConfigKeys() {
479479
return null;
480480
}
481481

482+
@Override
483+
public List<String> getApiNameList() {
484+
return null;
485+
}
482486
}

0 commit comments

Comments
 (0)