From cca1f3a5c5fd269f3b155d6e3b1d1dcb2e4e4a24 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Wed, 19 Jan 2022 18:26:05 +0100 Subject: [PATCH 01/11] prevent role access escallation --- .../java/com/cloud/user/AccountService.java | 4 +- .../org/apache/cloudstack/acl/APIChecker.java | 2 + .../admin/account/CreateAccountCmd.java | 3 +- .../admin/account/CreateAccountCmdTest.java | 6 +-- .../acl/DynamicRoleBasedAPIAccessChecker.java | 4 ++ .../acl/ProjectRoleBasedApiAccessChecker.java | 5 ++ .../acl/StaticRoleBasedAPIAccessChecker.java | 4 ++ .../management/MockAccountManager.java | 11 ++-- .../com/cloud/user/AccountManagerImpl.java | 50 ++++++++++++++++--- .../cloud/user/MockAccountManagerImpl.java | 11 ++-- 10 files changed, 79 insertions(+), 21 deletions(-) diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index 98b1618a8da8..338a8d3a7694 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -21,6 +21,7 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.RegisterCmd; import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; @@ -39,8 +40,7 @@ public interface AccountService { * Creates a new user and account, stores the password as is so encrypted passwords are recommended. * @return the user if created successfully, null otherwise */ - UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType, Long roleId, Long domainId, - String networkDomain, Map details, String accountUUID, String userUUID); + UserAccount createUserAccount(CreateAccountCmd accountCmd); UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType, Long roleId, Long domainId, String networkDomain, Map details, String accountUUID, String userUUID, User.Source source); diff --git a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java index 0d0dfd1be4e0..fa0576383a60 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.acl; import com.cloud.exception.PermissionDeniedException; +import com.cloud.user.Account; import com.cloud.user.User; import com.cloud.utils.component.Adapter; @@ -27,4 +28,5 @@ public interface APIChecker extends Adapter { // If false, apiChecker is unable to handle the operation or not implemented // On exception, checkAccess failed don't allow boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException; + boolean checkAccess(Account user, String apiCommandName) throws PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java index 508750a068cd..159f17f728cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java @@ -186,8 +186,7 @@ public void execute() { validateParams(); CallContext.current().setEventDetails("Account Name: " + getUsername() + ", Domain Id:" + getDomainId()); UserAccount userAccount = - _accountService.createUserAccount(getUsername(), getPassword(), getFirstName(), getLastName(), getEmail(), getTimeZone(), getAccountName(), getAccountType(), getRoleId(), - getDomainId(), getNetworkDomain(), getDetails(), getAccountUUID(), getUserUUID()); + _accountService.createUserAccount(this); if (userAccount != null) { AccountResponse response = _responseGenerator.createUserAccountResponse(ResponseView.Full, userAccount); response.setResponseName(getCommandName()); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java index b50b2269c176..cf32a67ff707 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java @@ -73,7 +73,7 @@ public void testExecuteWithNotBlankPassword() { } catch (ServerApiException e) { Assert.assertTrue("Received exception as the mock accountService createUserAccount returns null user", true); } - Mockito.verify(accountService, Mockito.times(1)).createUserAccount(null, "Test", null, null, null, null, null, accountType, roleId, domainId, null, null, null, null); + Mockito.verify(accountService, Mockito.times(1)).createUserAccount(createAccountCmd); } @Test @@ -86,7 +86,7 @@ public void testExecuteWithNullPassword() { Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode()); Assert.assertEquals("Empty passwords are not allowed", e.getMessage()); } - Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null); + Mockito.verify(accountService, Mockito.never()).createUserAccount(createAccountCmd); } @Test @@ -99,6 +99,6 @@ public void testExecuteWithEmptyPassword() { Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode()); Assert.assertEquals("Empty passwords are not allowed", e.getMessage()); } - Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null); + Mockito.verify(accountService, Mockito.never()).createUserAccount(createAccountCmd); } } diff --git a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java index f693bae8c33c..6c76229568c8 100644 --- a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java +++ b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java @@ -75,6 +75,10 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null"); } + return checkAccess(account, commandName); + } + + public boolean checkAccess(Account account, String commandName) { final Role accountRole = roleService.findRole(account.getRoleId()); if (accountRole == null || accountRole.getId() < 1L) { denyApiAccess(commandName); diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 5648a96ea666..42043eba5a33 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -103,6 +103,11 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account/user in project "+project.getUuid()); } + @Override + public boolean checkAccess(Account user, String apiCommandName) throws PermissionDeniedException { + throw new PermissionDeniedException("cannot ask permission for an account from a project context"); + } + private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { ProjectRole projectRole = null; if(projectUser.getProjectRoleId() != null) { diff --git a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java index 7550483b2306..64ba210f6ac1 100644 --- a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java +++ b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java @@ -80,6 +80,10 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null"); } + return checkAccess(account, commandName); + } + + public boolean checkAccess(Account account, String commandName) { RoleType roleType = accountService.getRoleType(account); boolean isAllowed = commandsPropertiesOverrides.contains(commandName) ? commandsPropertiesRoleBasedApisMap.get(roleType).contains(commandName) : annotationRoleBasedApisMap.get( diff --git a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java index 5cd90c930089..6326d7e95fc9 100644 --- a/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java +++ b/plugins/network-elements/juniper-contrail/src/test/java/org/apache/cloudstack/network/contrail/management/MockAccountManager.java @@ -24,6 +24,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -140,10 +141,12 @@ public User createUser(String arg0, String arg1, String arg2, String arg3, Strin } @Override - public UserAccount createUserAccount(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, String arg6, short arg7, Long roleId, Long arg8, String arg9, - Map arg10, String arg11, String arg12) { - // TODO Auto-generated method stub - return null; + public UserAccount createUserAccount(CreateAccountCmd cmd) { + return createUserAccount(cmd.getUsername(), cmd.getPassword(), cmd.getFirstName(), + cmd.getLastName(), cmd.getEmail(), cmd.getTimeZone(), cmd.getAccountName(), + cmd.getAccountType(), cmd.getRoleId(), cmd.getDomainId(), + cmd.getNetworkDomain(), cmd.getDetails(), cmd.getAccountUUID(), + cmd.getUserUUID(), User.Source.UNKNOWN); } @Override diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 88cd217698a3..30894db2431f 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -37,14 +37,19 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.QuerySelector; import org.apache.cloudstack.acl.Role; +import org.apache.cloudstack.acl.RolePermission; +import org.apache.cloudstack.acl.RolePermissionEntity.Permission; +import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; @@ -279,9 +284,12 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M private List _userAuthenticators; protected List _userPasswordEncoders; + private List apiAccessCheckers; @Inject private IpAddressManager _ipAddrMgr; + @Inject + private RoleService roleService; private final ScheduledExecutorService _executor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AccountChecker")); @@ -1003,14 +1011,16 @@ private boolean doDisableAccount(long accountId) throws ConcurrentOperationExcep @Override @ActionEvents({@ActionEvent(eventType = EventTypes.EVENT_ACCOUNT_CREATE, eventDescription = "creating Account"), - @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")}) - public UserAccount createUserAccount(final String userName, final String password, final String firstName, final String lastName, final String email, final String timezone, String accountName, - final short accountType, final Long roleId, Long domainId, final String networkDomain, final Map details, String accountUUID, final String userUUID) { - - return createUserAccount(userName, password, firstName, lastName, email, timezone, accountName, accountType, roleId, domainId, networkDomain, details, accountUUID, userUUID, - User.Source.UNKNOWN); + @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")}) + public UserAccount createUserAccount(CreateAccountCmd accountCmd) { + return createUserAccount(accountCmd.getUsername(), accountCmd.getPassword(), accountCmd.getFirstName(), + accountCmd.getLastName(), accountCmd.getEmail(), accountCmd.getTimeZone(), accountCmd.getAccountName(), + accountCmd.getAccountType(), accountCmd.getRoleId(), accountCmd.getDomainId(), + accountCmd.getNetworkDomain(), accountCmd.getDetails(), accountCmd.getAccountUUID(), + accountCmd.getUserUUID(), User.Source.UNKNOWN); } + // /////////////////////////////////////////////////// // ////////////// API commands ///////////////////// // /////////////////////////////////////////////////// @@ -1054,6 +1064,9 @@ public UserAccount createUserAccount(final String userName, final String passwor // Check permissions checkAccess(getCurrentCallingAccount(), domain); +// get user by account + checkRoleEscalation(getCurrentCallingAccount(), roleId); + if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { throw new InvalidParameterValueException("The user " + userName + " already exists in domain " + domainId); } @@ -1110,6 +1123,31 @@ public Pair doInTransaction(TransactionStatus status) { return _userAccountDao.findById(userId); } + /** + * if there is any permission under the requested role that is not permitted for the caller, refuse + */ + private void checkRoleEscalation(Account caller, Long requestedRoleId) { + List requestedPermissions = roleService.findAllPermissionsBy(requestedRoleId); + + for (RolePermission permission : requestedPermissions) { + if (permission.getPermission() == Permission.ALLOW) { + String command = permission.getRule().getRuleString(); + if (command.contains("*")) { + // find all api that match command and loop + checkApiAccess(caller, command); + } else { + checkApiAccess(caller, command); + } + } + } + } + + private void checkApiAccess(Account caller, String command) { + for (final APIChecker apiChecker : apiAccessCheckers) { + apiChecker.checkAccess(caller, command); + } + } + @Override @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User") public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID, diff --git a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java index 7916007c4065..f222368500ae 100644 --- a/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java +++ b/server/src/test/java/com/cloud/user/MockAccountManagerImpl.java @@ -22,6 +22,7 @@ import javax.naming.ConfigurationException; +import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; import org.apache.cloudstack.api.command.admin.user.MoveUserCmd; import org.apache.cloudstack.framework.config.ConfigKey; @@ -346,10 +347,12 @@ public UserAccount getUserByApiKey(String apiKey) { } @Override - public UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, - short accountType, Long roleId, Long domainId, String networkDomain, Map details, String accountUUID, String userUUID) { - // TODO Auto-generated method stub - return null; + public UserAccount createUserAccount(CreateAccountCmd cmd) { + return createUserAccount(cmd.getUsername(), cmd.getPassword(), cmd.getFirstName(), + cmd.getLastName(), cmd.getEmail(), cmd.getTimeZone(), cmd.getAccountName(), + cmd.getAccountType(), cmd.getRoleId(), cmd.getDomainId(), + cmd.getNetworkDomain(), cmd.getDetails(), cmd.getAccountUUID(), + cmd.getUserUUID(), User.Source.UNKNOWN); } @Override From fb37d92a7422fa1e313bce585a92f0e07ea171bf Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Thu, 20 Jan 2022 09:13:26 +0100 Subject: [PATCH 02/11] hierarchy issue fixed --- .../cloudstack/ratelimit/ApiRateLimitServiceImpl.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java index e35a332c54d1..fa0294182d20 100644 --- a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java +++ b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -147,16 +147,20 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe } Long accountId = user.getAccountId(); Account account = _accountService.getAccount(accountId); + return checkAccess(account, apiCommandName); + } + + public boolean checkAccess(Account account, String commandName) { if (_accountService.isRootAdmin(account.getId())) { // no API throttling on root admin return true; } - StoreEntry entry = _store.get(accountId); + StoreEntry entry = _store.get(account.getId()); if (entry == null) { /* Populate the entry, thus unlocking any underlying mutex */ - entry = _store.create(accountId, timeToLive); + entry = _store.create(account.getId(), timeToLive); } /* Increment the client count and see whether we have hit the maximum allowed clients yet. */ From 7cab7b8f986f9239add3dba05d429c6bddc5c628 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Thu, 20 Jan 2022 16:32:59 +0100 Subject: [PATCH 03/11] create api list in account manager for checking new account access --- .../org/apache/cloudstack/acl/APIChecker.java | 2 +- .../acl/ProjectRoleBasedApiAccessChecker.java | 4 +- .../discovery/ApiDiscoveryService.java | 5 ++ .../discovery/ApiDiscoveryServiceImpl.java | 19 +++++ .../com/cloud/user/AccountManagerImpl.java | 70 +++++++++++++++++-- .../spring-server-core-managers-context.xml | 4 +- 6 files changed, 96 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java index fa0576383a60..01237e43f160 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java @@ -28,5 +28,5 @@ public interface APIChecker extends Adapter { // If false, apiChecker is unable to handle the operation or not implemented // On exception, checkAccess failed don't allow boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException; - boolean checkAccess(Account user, String apiCommandName) throws PermissionDeniedException; + boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException; } diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 42043eba5a33..2b986276a3c8 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -104,8 +104,8 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe } @Override - public boolean checkAccess(Account user, String apiCommandName) throws PermissionDeniedException { - throw new PermissionDeniedException("cannot ask permission for an account from a project context"); + public boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException { + return true; } private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) { diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java index ce8722e01820..5a6eab7389d1 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java @@ -16,12 +16,17 @@ // under the License. package org.apache.cloudstack.discovery; +import com.cloud.user.Account; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.response.ListResponse; import com.cloud.user.User; import com.cloud.utils.component.PluggableService; +import java.util.List; + public interface ApiDiscoveryService extends PluggableService { + List listApiNames(Account account); + ListResponse listApis(User user, String apiName); } diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java index 5d2285629d65..0a0a608434de 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java @@ -27,6 +27,7 @@ import javax.inject.Inject; +import com.cloud.user.Account; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.BaseAsyncCmd; @@ -210,6 +211,24 @@ private ApiDiscoveryResponse getCmdRequestMap(Class cmdClass, APICommand apiC return response; } + @Override + public List listApiNames(Account account) { + List apiNames = new ArrayList<>(); + for (String apiName : s_apiNameDiscoveryResponseMap.keySet()) { + boolean isAllowed = true; + for (APIChecker apiChecker : _apiAccessCheckers) { + try { + apiChecker.checkAccess(account, apiName); + } catch (Exception ex) { + isAllowed = false; + } + } + if (isAllowed) + apiNames.add(apiName); + } + return apiNames; + } + @Override public ListResponse listApis(User user, String name) { ListResponse response = new ListResponse(); diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 30894db2431f..352828064c18 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -23,8 +23,10 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -37,6 +39,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.component.PluggableService; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.QuerySelector; @@ -49,6 +52,7 @@ import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; +import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd; import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd; @@ -284,6 +288,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M private List _userAuthenticators; protected List _userPasswordEncoders; + protected List services; private List apiAccessCheckers; @Inject @@ -300,6 +305,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M private List _securityCheckers; private int _cleanupInterval; + private List apiNameList; + + protected AccountManagerImpl() { + super(); + } public List getUserAuthenticators() { return _userAuthenticators; @@ -325,6 +335,22 @@ public void setSecurityCheckers(List securityCheckers) { _securityCheckers = securityCheckers; } + public List getServices() { + return services; + } + + public void setServices(List services) { + this.services = services; + } + + public List getApiAccessCheckers() { + return apiAccessCheckers; + } + + public void setApiAccessCheckers(List apiAccessCheckers) { + this.apiAccessCheckers = apiAccessCheckers; + } + public List getQuerySelectors() { return _querySelectors; } @@ -366,10 +392,46 @@ public UserVO getSystemUser() { @Override public boolean start() { + if (apiNameList == null) { + long startTime = System.nanoTime(); + apiNameList = new ArrayList(); + Set> cmdClasses = new LinkedHashSet>(); + for (PluggableService service : services) { + s_logger.debug(String.format("getting api commands of service: %s", service.getClass().getName())); + cmdClasses.addAll(service.getCommands()); + } + apiNameList = createApiNameList(cmdClasses); + long endTime = System.nanoTime(); + s_logger.info("Api Discovery Service: Annotation, docstrings, api relation graph processed in " + (endTime - startTime) / 1000000.0 + " ms"); + } _executor.scheduleAtFixedRate(new AccountCleanupTask(), _cleanupInterval, _cleanupInterval, TimeUnit.SECONDS); return true; } + protected List createApiNameList(Set> cmdClasses) { + List apiNameList = new ArrayList(); + + for (Class cmdClass : cmdClasses) { + APICommand apiCmdAnnotation = cmdClass.getAnnotation(APICommand.class); + if (apiCmdAnnotation == null) { + apiCmdAnnotation = cmdClass.getSuperclass().getAnnotation(APICommand.class); + } + if (apiCmdAnnotation == null || !apiCmdAnnotation.includeInApiDoc() || apiCmdAnnotation.name().isEmpty()) { + continue; + } + + String apiName = apiCmdAnnotation.name(); + if (s_logger.isTraceEnabled()) { + s_logger.trace("Found api: " + apiName); + } + + apiNameList.add(apiName); + } + + return apiNameList; + } + + @Override public boolean stop() { return true; @@ -1064,9 +1126,6 @@ public UserAccount createUserAccount(final String userName, final String passwor // Check permissions checkAccess(getCurrentCallingAccount(), domain); -// get user by account - checkRoleEscalation(getCurrentCallingAccount(), roleId); - if (!_userAccountDao.validateUsernameInDomain(userName, domainId)) { throw new InvalidParameterValueException("The user " + userName + " already exists in domain " + domainId); } @@ -1093,6 +1152,8 @@ public Pair doInTransaction(TransactionStatus status) { AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, accountUUID); long accountId = account.getId(); + checkRoleEscalation(getCurrentCallingAccount(), account); + // create the first user for the account UserVO user = createUser(accountId, userName, password, firstName, lastName, email, timezone, userUUID, source); @@ -1126,7 +1187,8 @@ public Pair doInTransaction(TransactionStatus status) { /** * if there is any permission under the requested role that is not permitted for the caller, refuse */ - private void checkRoleEscalation(Account caller, Long requestedRoleId) { + private void checkRoleEscalation(Account caller, Account requested) { + Long requestedRoleId = requested.getRoleId(); List requestedPermissions = roleService.findAllPermissionsBy(requestedRoleId); for (RolePermission permission : requestedPermissions) { diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index d79908ecedf7..30248d73a364 100644 --- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -47,7 +47,9 @@ - + + + From 777d6bf89d0d2bac7ed88512bde250de35a3e8aa Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Fri, 21 Jan 2022 11:33:43 +0100 Subject: [PATCH 04/11] full api list check --- .../com/cloud/user/AccountManagerImpl.java | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 352828064c18..b2c2a37412bc 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -45,7 +45,6 @@ import org.apache.cloudstack.acl.QuerySelector; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RolePermission; -import org.apache.cloudstack.acl.RolePermissionEntity.Permission; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; @@ -1190,16 +1189,23 @@ public Pair doInTransaction(TransactionStatus status) { private void checkRoleEscalation(Account caller, Account requested) { Long requestedRoleId = requested.getRoleId(); List requestedPermissions = roleService.findAllPermissionsBy(requestedRoleId); - - for (RolePermission permission : requestedPermissions) { - if (permission.getPermission() == Permission.ALLOW) { - String command = permission.getRule().getRuleString(); - if (command.contains("*")) { - // find all api that match command and loop - checkApiAccess(caller, command); - } else { - checkApiAccess(caller, command); - } + for (String command : apiNameList) { + // if requested can, make sure caller can as well + try { + checkApiAccess(requested,command); + } catch (PermissionDeniedException pde) { + continue; + } + try { + checkApiAccess(caller, command); + } catch (PermissionDeniedException pde) { + String msg = String.format("user of account %s/%s (%s) can not create an account with access to %s", + caller.getAccountName(), + caller.getDomainId(), + caller.getUuid(), + command); + s_logger.warn(msg); + throw new PermissionDeniedException(msg,pde); } } } From b3e9b14ee266aafa1a83807bd3f6a807aac89281 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Tue, 25 Jan 2022 11:42:03 +0100 Subject: [PATCH 05/11] strange role restriction removed for BareMetal --- .../cloudstack/api/BaremetalProvisionDoneNotificationCmd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java index a5248aaec65d..f53ac063301e 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java @@ -35,7 +35,7 @@ * Created by frank on 9/17/14. */ @APICommand(name = "notifyBaremetalProvisionDone", description = "Notify provision has been done on a host. This api is for baremetal virtual router service, not for end user", responseObject = SuccessResponse.class, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.User}) + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class BaremetalProvisionDoneNotificationCmd extends BaseAsyncCmd { public static final Logger s_logger = Logger.getLogger(BaremetalProvisionDoneNotificationCmd.class); private static final String s_name = "baremetalprovisiondone"; From 4f744e411cd60bcf455b646cc2d16f206aa0f5dc Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Tue, 25 Jan 2022 13:17:35 +0100 Subject: [PATCH 06/11] rat --- .../cloudstack/api/BaremetalProvisionDoneNotificationCmd.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java index f53ac063301e..c712849a27ac 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java @@ -24,7 +24,6 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; From 9ccc476ad2bc548a5dc2a02ac7c452a033b074a9 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Wed, 26 Jan 2022 10:56:49 +0100 Subject: [PATCH 07/11] add role check on upfdate account as well --- server/src/main/java/com/cloud/user/AccountManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index b2c2a37412bc..eae251f5c0aa 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1188,7 +1188,6 @@ public Pair doInTransaction(TransactionStatus status) { */ private void checkRoleEscalation(Account caller, Account requested) { Long requestedRoleId = requested.getRoleId(); - List requestedPermissions = roleService.findAllPermissionsBy(requestedRoleId); for (String command : apiNameList) { // if requested can, make sure caller can as well try { @@ -1865,6 +1864,7 @@ public AccountVO updateAccount(UpdateAccountCmd cmd) { } acctForUpdate.setRoleId(roleId); + checkRoleEscalation(getCurrentCallingAccount(), acctForUpdate); } if (networkDomain != null) { From d7b6c27af20a0554653dffc3334d42a2a0a553d6 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Wed, 26 Jan 2022 14:07:10 +0100 Subject: [PATCH 08/11] comment --- server/src/main/java/com/cloud/user/AccountManagerImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index eae251f5c0aa..c3e2c8833840 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1189,12 +1189,12 @@ public Pair doInTransaction(TransactionStatus status) { private void checkRoleEscalation(Account caller, Account requested) { Long requestedRoleId = requested.getRoleId(); for (String command : apiNameList) { - // if requested can, make sure caller can as well try { checkApiAccess(requested,command); } catch (PermissionDeniedException pde) { continue; } + // so requested can, now make sure caller can as well try { checkApiAccess(caller, command); } catch (PermissionDeniedException pde) { From eb78064bf864756c95387ea6e070d177502ccf13 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Thu, 27 Jan 2022 09:48:12 +0100 Subject: [PATCH 09/11] import --- server/src/main/java/com/cloud/user/AccountManagerImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index c3e2c8833840..37f2e9ffd60e 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -44,7 +44,6 @@ import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.QuerySelector; import org.apache.cloudstack.acl.Role; -import org.apache.cloudstack.acl.RolePermission; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; From 7ee51142b99a02027530d53bbe34581c464fa626 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Wed, 2 Feb 2022 14:00:26 +0100 Subject: [PATCH 10/11] more selective use of api checkers --- .../org/apache/cloudstack/acl/APIChecker.java | 1 + .../acl/DynamicRoleBasedAPIAccessChecker.java | 5 ++ .../acl/ProjectRoleBasedApiAccessChecker.java | 6 ++- .../acl/StaticRoleBasedAPIAccessChecker.java | 4 ++ .../ratelimit/ApiRateLimitServiceImpl.java | 5 ++ .../com/cloud/user/AccountManagerImpl.java | 48 +++++++++++++++++-- 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java index 01237e43f160..6cf45456e785 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java @@ -29,4 +29,5 @@ public interface APIChecker extends Adapter { // On exception, checkAccess failed don't allow boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException; boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException; + boolean isEnabled(); } diff --git a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java index 6c76229568c8..76d700eec02f 100644 --- a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java +++ b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java @@ -110,6 +110,11 @@ public boolean checkAccess(Account account, String commandName) { throw new UnavailableCommandException("The API " + commandName + " does not exist or is not available for this account."); } + @Override + public boolean isEnabled() { + return roleService.isEnabled(); + } + public void addApiToRoleBasedAnnotationsMap(final RoleType roleType, final String commandName) { if (roleType == null || Strings.isNullOrEmpty(commandName)) { return; diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 2b986276a3c8..4d33b26c428a 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -58,9 +58,13 @@ private void denyApiAccess(final String commandName) throws PermissionDeniedExce throw new PermissionDeniedException("The API " + commandName + " is denied for the user's/account's project role."); } + @Override + public boolean isEnabled() { + return roleService.isEnabled(); + } public boolean isDisabled() { - return !roleService.isEnabled(); + return !isEnabled(); } @Override diff --git a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java index 64ba210f6ac1..2b2464f720cd 100644 --- a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java +++ b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java @@ -65,6 +65,10 @@ public StaticRoleBasedAPIAccessChecker() { } } + @Override + public boolean isEnabled() { + return !isDisabled(); + } public boolean isDisabled() { return roleService.isEnabled(); } diff --git a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java index fa0294182d20..3ed3551414ae 100644 --- a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java +++ b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -178,6 +178,11 @@ public boolean checkAccess(Account account, String commandName) { } } + @Override + public boolean isEnabled() { + return enabled; + } + @Override public List> getCommands() { List> cmdList = new ArrayList>(); diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 37f2e9ffd60e..f3fb0805d26b 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -183,6 +183,7 @@ import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import org.jetbrains.annotations.NotNull; public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager { public static final Logger s_logger = Logger.getLogger(AccountManagerImpl.class); @@ -1186,16 +1187,37 @@ public Pair doInTransaction(TransactionStatus status) { * if there is any permission under the requested role that is not permitted for the caller, refuse */ private void checkRoleEscalation(Account caller, Account requested) { - Long requestedRoleId = requested.getRoleId(); + if (s_logger.isDebugEnabled()) { + s_logger.debug(String.format("checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]", + caller.getAccountName(), + caller.getUuid(), + caller.getRoleId(), + requested.getAccountName(), + requested.getUuid(), + requested.getRoleId())); + } + List apiCheckers = getEnabledApiCheckers(); for (String command : apiNameList) { try { - checkApiAccess(requested,command); + checkApiAccess(apiCheckers, requested, command); } catch (PermissionDeniedException pde) { + if (s_logger.isTraceEnabled()) { + s_logger.trace(String.format("checking for permission to \"%s\" is irrelevant as it is not requested for %s [%s]", + command, + pde.getAccount().getAccountName(), + pde.getAccount().getUuid(), + pde.getEntitiesInViolation() + )); + } continue; } // so requested can, now make sure caller can as well try { - checkApiAccess(caller, command); + if (s_logger.isTraceEnabled()) { + s_logger.trace(String.format("permission to \"%s\" is requested", + command)); + } + checkApiAccess(apiCheckers, caller, command); } catch (PermissionDeniedException pde) { String msg = String.format("user of account %s/%s (%s) can not create an account with access to %s", caller.getAccountName(), @@ -1208,12 +1230,28 @@ private void checkRoleEscalation(Account caller, Account requested) { } } - private void checkApiAccess(Account caller, String command) { - for (final APIChecker apiChecker : apiAccessCheckers) { + private void checkApiAccess(List apiCheckers, Account caller, String command) { + for (final APIChecker apiChecker : apiCheckers) { apiChecker.checkAccess(caller, command); } } + @NotNull + private List getEnabledApiCheckers() { + // we are really only interested in the dynamic access checker + List usableApiCheckera = new ArrayList<>(); + for (APIChecker apiChecker : apiAccessCheckers) { + if (apiChecker.isEnabled()) { + usableApiCheckera.add(apiChecker); + if (s_logger.isTraceEnabled()) { + s_logger.trace(String.format("using api checker \"%s\"", + apiChecker.getName())); + } + } + } + return usableApiCheckera; + } + @Override @ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User") public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID, From 91bf7b107cd4eb165773e37cac3b9576374ed662 Mon Sep 17 00:00:00 2001 From: Daan Hoogland Date: Fri, 4 Feb 2022 14:57:18 +0100 Subject: [PATCH 11/11] error msg and var name --- .../main/java/com/cloud/user/AccountManagerImpl.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index f3fb0805d26b..bd51cd419e54 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1219,11 +1219,10 @@ private void checkRoleEscalation(Account caller, Account requested) { } checkApiAccess(apiCheckers, caller, command); } catch (PermissionDeniedException pde) { - String msg = String.format("user of account %s/%s (%s) can not create an account with access to %s", + String msg = String.format("User of Account %s/%s (%s) can not create an account with access to more privileges they have themself.", caller.getAccountName(), caller.getDomainId(), - caller.getUuid(), - command); + caller.getUuid()); s_logger.warn(msg); throw new PermissionDeniedException(msg,pde); } @@ -1239,17 +1238,17 @@ private void checkApiAccess(List apiCheckers, Account caller, String @NotNull private List getEnabledApiCheckers() { // we are really only interested in the dynamic access checker - List usableApiCheckera = new ArrayList<>(); + List usableApiCheckers = new ArrayList<>(); for (APIChecker apiChecker : apiAccessCheckers) { if (apiChecker.isEnabled()) { - usableApiCheckera.add(apiChecker); + usableApiCheckers.add(apiChecker); if (s_logger.isTraceEnabled()) { s_logger.trace(String.format("using api checker \"%s\"", apiChecker.getName())); } } } - return usableApiCheckera; + return usableApiCheckers; } @Override