API key pair restructure follow-ups (#13828)
Some checks are pending
CodeQL Analysis / CodeQL (actions) (push) Waiting to run
Docker Image Build / build (push) Waiting to run
Sonar Quality Check (Main) / Sonar JaCoCo Coverage (push) Waiting to run

This commit is contained in:
Bernardo De Marco Gonçalves 2026-08-20 09:44:27 -03:00 committed by GitHub
parent d45d48180a
commit 4bfeb96c96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 109 additions and 61 deletions

View File

@ -185,4 +185,6 @@ public interface AccountService {
String getAccessingApiKey(BaseCmd cmd);
List<RolePermissionEntity> getAllKeypairPermissions(String apiKey);
List<? extends ApiKeyPairPermission> getAllExplicitKeyPairPermissions(Long keyPairId);
}

View File

@ -20,6 +20,7 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.utils.component.Adapter;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import java.util.List;
@ -32,8 +33,8 @@ public interface APIChecker extends Adapter {
// If true, apiChecker has checked the operation
// 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, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
boolean checkAccess(User user, String apiCommandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
/**
* Verifies if the account has permission for the given list of APIs and returns only the allowed ones.
*

View File

@ -28,6 +28,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.api.APICommand;
@ -141,7 +142,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
}
@Override
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -150,11 +151,11 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
throw new PermissionDeniedException(String.format("Account for user with ID [%s] cannot be found", user.getUuid()));
}
return checkAccess(account, commandName, apiKeyPairPermissions);
return checkAccess(account, commandName, keyPair, apiKeyPairPermissions);
}
@Override
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) {
public boolean checkAccess(Account account, String commandName, ApiKeyPair keyPair, ApiKeyPairPermission ... apiKeyPairPermissions) {
Pair<Role, List<RolePermission>> roleAndPermissions = getRolePermissionsUsingCache(account.getRoleId());
final Role accountRole = roleAndPermissions.first();
if (accountRole == null) {
@ -166,7 +167,8 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
return true;
}
boolean considerKeyPairPermissions = apiKeyPairPermissions.length > 0;
boolean keyPairHasExplicitPermissions = keyPair != null && !accountService.getAllExplicitKeyPairPermissions(keyPair.getId()).isEmpty();
boolean considerKeyPairPermissions = apiKeyPairPermissions.length > 0 || keyPairHasExplicitPermissions;
List<RolePermissionEntity> allRules = considerKeyPairPermissions ? Arrays.asList(apiKeyPairPermissions) : new ArrayList<>(roleAndPermissions.second());
if (checkApiPermissionByRole(accountRole, commandName, allRules, considerKeyPairPermissions)) {
return true;

View File

@ -85,14 +85,14 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
// Enabled plugin
Mockito.doReturn(true).when(apiAccessCheckerSpy).isEnabled();
Mockito.doCallRealMethod().when(apiAccessCheckerSpy).checkAccess(Mockito.any(User.class), Mockito.anyString());
Mockito.doCallRealMethod().when(apiAccessCheckerSpy).checkAccess(Mockito.any(User.class), Mockito.anyString(), Mockito.any());
}
@Test
public void testInvalidAccountCheckAccess() {
Mockito.when(accountService.getAccount(Mockito.anyLong())).thenReturn(null);
try {
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi");
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi", null);
fail("Exception was expected");
} catch (PermissionDeniedException ignored) {
}
@ -102,7 +102,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
public void testInvalidAccountRoleCheckAccess() {
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(null);
try {
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi");
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi", null);
fail("Exception was expected");
} catch (PermissionDeniedException ignored) {
}
@ -112,14 +112,14 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
public void testDefaultRootAdminAccess() {
Mockito.when(accountService.getAccount(Mockito.anyLong())).thenReturn(new AccountVO("root admin", 1L, null, Account.Type.ADMIN, "some-uuid"));
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(new RoleVO(1L, "SomeRole", RoleType.Admin, "default root admin role"));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), "anyApi"));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), "anyApi", null));
}
@Test
public void testInvalidRolePermissionsCheckAccess() {
Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.<RolePermission>emptyList());
try {
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi");
apiAccessCheckerSpy.checkAccess(getTestUser(), "someApi", null);
fail("Exception was expected");
} catch (PermissionDeniedException ignored) {
}
@ -130,7 +130,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final String allowedApiName = "someAllowedApi";
final RolePermission permission = new RolePermissionVO(1L, allowedApiName, Permission.ALLOW, null);
Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName, null));
}
@Test
@ -138,7 +138,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final String allowedApiName = "someAllowedApi";
final RolePermission permission = new RolePermissionVO(1L, "some*", Permission.ALLOW, null);
Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName, null));
}
@Test
@ -147,7 +147,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final RolePermission permission = new RolePermissionVO(1L, denyApiName, Permission.DENY, null);
Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
try {
apiAccessCheckerSpy.checkAccess(getTestUser(), denyApiName);
apiAccessCheckerSpy.checkAccess(getTestUser(), denyApiName, null);
fail("Exception was expected");
} catch (PermissionDeniedException ignored) {
}
@ -159,7 +159,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final RolePermission permission = new RolePermissionVO(1L, "*Deny*", Permission.DENY, null);
Mockito.when(roleServiceMock.findAllPermissionsBy(Mockito.anyLong())).thenReturn(Collections.singletonList(permission));
try {
apiAccessCheckerSpy.checkAccess(getTestUser(), denyApiName);
apiAccessCheckerSpy.checkAccess(getTestUser(), denyApiName, null);
fail("Exception was expected");
} catch (PermissionDeniedException ignored) {
}
@ -169,7 +169,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
public void testAnnotationFallbackCheckAccess() {
final String allowedApiName = "someApiWithAnnotations";
apiAccessCheckerSpy.addApiToRoleBasedAnnotationsMap(getTestRole().getRoleType(), allowedApiName);
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), allowedApiName, null));
}
@Test
@ -202,21 +202,21 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
public void checkAccessTestInvalidApiKeyPairPermission() {
final String api = "someDeniedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null);
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, permission));
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestUnrelatedApiKeyPairPermission() {
final String api = "someDeniedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, "apiName", Permission.ALLOW, null);
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, permission));
}
@Test
public void checkAccessTestValidApiKeyPairPermission() {
final String api = "someAllowedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null);
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, permission));
}
@Test
@ -226,7 +226,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
new ApiKeyPairPermissionVO(1L, "someDeniedApi", Permission.DENY, null),
new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null)
};
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, permissions));
}
@Test(expected = UnavailableCommandException.class)
@ -236,7 +236,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
new ApiKeyPairPermissionVO(1L, "someAllowedApi", Permission.ALLOW, null),
new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null)
};
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, permissions));
}
@ -247,7 +247,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final RolePermission permission = new RolePermissionVO(1L, api, Permission.ALLOW, null);
Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, emptyPermissionArray));
Mockito.verify(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
}
@ -258,7 +258,7 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
final RolePermission permission = new RolePermissionVO(1L, api, Permission.DENY, null);
Mockito.doReturn(Collections.singletonList(permission)).when(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, emptyPermissionArray));
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, null, emptyPermissionArray));
Mockito.verify(roleServiceMock, Mockito.times(1)).findAllPermissionsBy(Mockito.anyLong());
}
}

View File

@ -23,6 +23,7 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.context.CallContext;
@ -106,7 +107,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -151,7 +152,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
public boolean checkAccess(Account account, String apiCommandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
return true;
}

View File

@ -27,6 +27,7 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.UnavailableCommandException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.APICommand;
@ -92,7 +93,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
}
@Override
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -102,11 +103,11 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
throw new PermissionDeniedException(String.format("The account with id [%s] for user with uuid [%s] is null.", user.getAccountId(), user.getUuid()));
}
return checkAccess(account, commandName);
return checkAccess(account, commandName, keyPair);
}
@Override
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) {
public boolean checkAccess(Account account, String commandName, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) {
if (!isEnabled()) {
return true;
}

View File

@ -252,7 +252,7 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
boolean isAllowed = true;
for (APIChecker apiChecker : _apiAccessCheckers) {
try {
apiChecker.checkAccess(account, apiName);
apiChecker.checkAccess(account, apiName, null);
} catch (Exception ex) {
isAllowed = false;
}
@ -287,7 +287,7 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
for (APIChecker apiChecker : _apiAccessCheckers) {
try {
apiChecker.checkAccess(user, name);
apiChecker.checkAccess(user, name, null);
} catch (Exception ex) {
logger.error(String.format("API discovery access check failed for [%s] with error [%s].", name, ex.getMessage()), ex);
return null;

View File

@ -29,6 +29,7 @@ import net.sf.ehcache.CacheManager;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.springframework.stereotype.Component;
@ -164,17 +165,17 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker,
}
@Override
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPair keyPair, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
Account account = _accountService.getAccount(user.getAccountId());
return checkAccess(account, apiCommandName, apiKeyPairPermissions);
return checkAccess(account, apiCommandName, keyPair, apiKeyPairPermissions);
}
@Override
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) {
public boolean checkAccess(Account account, String commandName, ApiKeyPair keyPair, ApiKeyPairPermission ... apiKeyPairPermissions) {
Long accountId = account.getAccountId();
if (_accountService.isRootAdmin(accountId)) {
logger.info(String.format("Account [%s] is Root Admin, in this case, API limit does not apply.",

View File

@ -89,7 +89,7 @@ public static void setUp() throws ConfigurationException {
private boolean isUnderLimit(User key) {
try {
s_limitService.checkAccess(key, null);
s_limitService.checkAccess(key, null, null);
return true;
} catch (RequestLimitException ex) {
return false;

View File

@ -632,4 +632,9 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
public Account getAccountByUuid(String accountUuid) {
return null;
}
@Override
public List<? extends ApiKeyPairPermission> getAllExplicitKeyPairPermissions(Long keyPairId) {
return null;
}
}

View File

@ -1007,7 +1007,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
// if userId not null, that mean that user is logged in
if (userId != null) {
final User user = ApiDBUtils.findUserById(userId);
return commandAvailable(remoteAddress, commandName, user);
return commandAvailable(remoteAddress, commandName, user, null);
} else {
if (commandName.equalsIgnoreCase(ListGuiThemesCmd.class.getAnnotation(APICommand.class).name())) {
return true;
@ -1120,7 +1120,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return false;
}
if (!commandAvailable(remoteAddress, commandName, user)) {
if (!commandAvailable(remoteAddress, commandName, user, null)) {
return false;
}
@ -1156,7 +1156,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
CallContext.register(user, account);
List<ApiKeyPairPermission> keyPairPermissions = keyPairManager.findAllPermissionsByKeyPairId(keyPair.getId(), account.getRoleId());
if (commandAvailable(remoteAddress, commandName, user, keyPairPermissions.toArray(new ApiKeyPairPermission[0]))) {
if (commandAvailable(remoteAddress, commandName, user, keyPair, keyPairPermissions.toArray(new ApiKeyPairPermission[0]))) {
logger.info("API accessed through API Key Pair. API Key: [{}].", keyPair.getApiKey());
return true;
}
@ -1170,9 +1170,9 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return false;
}
private boolean commandAvailable(final InetAddress remoteAddress, final String commandName, final User user, ApiKeyPairPermission... rolePermissions) {
private boolean commandAvailable(final InetAddress remoteAddress, final String commandName, final User user, ApiKeyPair keyPair, ApiKeyPairPermission... rolePermissions) {
try {
checkCommandAvailable(user, commandName, remoteAddress, rolePermissions);
checkCommandAvailable(user, commandName, remoteAddress, keyPair, rolePermissions);
} catch (final RequestLimitException ex) {
logger.debug(ex.getMessage());
throw new ServerApiException(ApiErrorCode.API_LIMIT_EXCEED, ex.getMessage());
@ -1465,7 +1465,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return domainIdArr[0];
}
private void checkCommandAvailable(final User user, final String commandName, final InetAddress remoteAddress, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
private void checkCommandAvailable(final User user, final String commandName, final InetAddress remoteAddress, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (user == null) {
throw new PermissionDeniedException("User is null for role based API access check for command" + commandName);
}
@ -1483,7 +1483,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
}
for (final APIChecker apiChecker : apiAccessCheckers) {
apiChecker.checkAccess(user, commandName, apiKeyPairPermissions);
apiChecker.checkAccess(user, commandName, keyPair, apiKeyPairPermissions);
}
}

View File

@ -45,12 +45,14 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.serializer.GsonHelper;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.projects.dao.ProjectInvitationDao;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.SSHKeyPairDao;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.user.dao.UserDao;
import com.google.gson.reflect.TypeToken;
import com.cloud.utils.db.TransactionCallbackWithException;
import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
@ -104,6 +106,7 @@ import org.apache.cloudstack.dns.DnsZone;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.kms.KMSManager;
@ -1506,7 +1509,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
List<APIChecker> apiCheckers = getEnabledApiCheckers();
for (String command : apiNameList) {
try {
checkApiAccess(apiCheckers, requested, command);
checkApiAccess(apiCheckers, requested, command, null);
} catch (PermissionDeniedException pde) {
if (logger.isTraceEnabled()) {
logger.trace(String.format(
@ -1525,7 +1528,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
logger.trace(String.format("permission to \"%s\" is requested",
command));
}
checkApiAccess(apiCheckers, caller, command);
checkApiAccess(apiCheckers, caller, command, null);
} catch (PermissionDeniedException pde) {
String msg = String.format("User of Account %s and domain %s can not create an account with access to more privileges they have themself.",
caller, _domainMgr.getDomain(caller.getDomainId()));
@ -1535,9 +1538,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
}
private void checkApiAccess(List<APIChecker> apiCheckers, Account caller, String command, ApiKeyPairPermission... apiKeyPairPermissions) {
private void checkApiAccess(List<APIChecker> apiCheckers, Account caller, String command, ApiKeyPair keyPair, ApiKeyPairPermission... apiKeyPairPermissions) {
for (final APIChecker apiChecker : apiCheckers) {
apiChecker.checkAccess(caller, command, apiKeyPairPermissions);
apiChecker.checkAccess(caller, command, keyPair, apiKeyPairPermissions);
}
}
@ -1546,20 +1549,22 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
List<APIChecker> apiCheckers = getEnabledApiCheckers();
List<ApiKeyPairPermission> keyPairPermissions = new ArrayList<>();
ApiKeyPair keyPair = null;
if (apiKey != null) {
Ternary<User, Account, ApiKeyPair> keyPairTernary = findUserByApiKey(apiKey);
if (keyPairTernary != null) {
keyPairPermissions = keyPairManager.findAllPermissionsByKeyPairId(keyPairTernary.third().getId(), caller.getRoleId());
keyPair = keyPairTernary.third();
}
}
checkApiAccess(apiCheckers, caller, command, keyPairPermissions.toArray(new ApiKeyPairPermission[0]));
checkApiAccess(apiCheckers, caller, command, keyPair, keyPairPermissions.toArray(new ApiKeyPairPermission[0]));
}
@Override
public void checkApiAccess(Account caller, String command) {
List<APIChecker> apiCheckers = getEnabledApiCheckers();
checkApiAccess(apiCheckers, caller, command);
checkApiAccess(apiCheckers, caller, command, null);
}
@NotNull
@ -3385,24 +3390,29 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
public String getAccessingApiKey(BaseCmd cmd) {
try {
if (cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd) cmd).getJob().toString().contains("\"signature\"")) {
return parseApiKeyFromAsyncJob((BaseAsyncCmd) cmd);
Map<String, String> requestPayload = cmd.getFullUrlParams();
if (cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd) cmd).getJob() instanceof AsyncJobVO) {
String asyncJobPayload = ((AsyncJobVO) ((BaseAsyncCmd) cmd).getJob()).getCmdInfo();
requestPayload = GsonHelper.getGson().fromJson(asyncJobPayload, new TypeToken<HashMap<String, String>>() {}.getType());
}
boolean accessedByApiKey = cmd.getFullUrlParams().containsKey(ApiConstants.SIGNATURE);
String accessingApiKey = cmd.getFullUrlParams().get("apiKey");
boolean accessedByApiKey = requestPayload.keySet().stream().anyMatch(ApiConstants.SIGNATURE::equalsIgnoreCase);
if (accessedByApiKey) {
return accessingApiKey;
String apiKey = requestPayload.entrySet().stream()
.filter(e -> ApiConstants.API_KEY.equalsIgnoreCase(e.getKey()))
.map(Map.Entry::getValue).findFirst().orElse(null);
if (apiKey != null) {
logger.info("Request's API key is [{}].", apiKey);
return apiKey;
}
}
} catch (NullPointerException e) {
logger.info("Accessing API through session.");
logger.warn("Unable to identify request API key due to: {}.", e);
}
return null;
}
private String parseApiKeyFromAsyncJob(BaseAsyncCmd cmd) {
String jobString = cmd.getJob().toString();
int indexOfApiKey = jobString.indexOf("apiKey") + 9;
return jobString.substring(indexOfApiKey, jobString.indexOf("\"", indexOfApiKey));
logger.info("Request's signature or API key were not identified; assuming it has been authenticated via session.");
return null;
}
private Boolean isApiKeySupersetOfPermission(List<RolePermissionEntity> baseKeyPairPermissions, List<RolePermissionEntity> comparedPermissions) {
@ -3453,8 +3463,13 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
internalDeleteApiKey(keyPair);
}
@Override
public List<? extends ApiKeyPairPermission> getAllExplicitKeyPairPermissions(Long keyPairId) {
return apiKeyPairPermissionsDao.findAllByApiKeyPairId(keyPairId);
}
private void internalDeleteApiKey(ApiKeyPair keyPair) {
List<ApiKeyPairPermissionVO> permissions = apiKeyPairPermissionsDao.findAllByApiKeyPairId(keyPair.getId());
List<? extends ApiKeyPairPermission> permissions = getAllExplicitKeyPairPermissions(keyPair.getId());
for (ApiKeyPairPermission permission : permissions) {
apiKeyPairPermissionsDao.remove(permission.getId());
}
@ -3622,6 +3637,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
permissions.add(new ApiKeyPairPermissionVO(0, rule, rulePermission, ruleDescription));
}
if (permissions.isEmpty() && accessingApiKey != null && doesKeyPairHaveExplicitPermissions(accessingApiKey)) {
logger.debug("No rules were specified for the new API key pair. Since the accessing API key [{}]" +
" has explicit permissions, these permissions will be defined as the rule set for the new pair.", accessingApiKey);
permissions = allPermissions.stream().map(permission -> (
new ApiKeyPairPermissionVO(0, permission.getRule().getRuleString(), permission.getPermission(), permission.getDescription())
)).collect(Collectors.toList());
}
if (!isApiKeySupersetOfPermission(allPermissions, permissions)) {
throw new InvalidParameterValueException(String.format("The key pair being created has a bigger set of permissions than the account [%s] " +
"that owns it. This is not allowed.", account.getUuid()));
@ -3636,6 +3659,16 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return savedApiKeyPair;
}
private boolean doesKeyPairHaveExplicitPermissions(String apiKey) {
ApiKeyPair apiKeyPair = keyPairManager.findByApiKey(apiKey);
if (apiKeyPair == null) {
logger.info("Unable to find API key pair entity with the API key [{}].", apiKey);
return false;
}
return !getAllExplicitKeyPairPermissions(apiKeyPair.getId()).isEmpty();
}
@Override
public List<RolePermissionEntity> getAllKeypairPermissions(String apiKey) {
if (apiKey == null) {

View File

@ -3683,7 +3683,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
if (checkExpunge && expunge) {
String jobParamsString = ((AsyncJobVO) cmd.getJob()).getCmdInfo();
HashMap<String,String> jobParams = GsonHelper.getGson().fromJson(jobParamsString, jobParamsType);
String apiKey = jobParams.get("apiKey");
String apiKey = jobParams.entrySet().stream()
.filter(e -> ApiConstants.API_KEY.equalsIgnoreCase(e.getKey()))
.map(Map.Entry::getValue).findFirst().orElse(null);
checkExpungeVmPermission(ctx.getCallingAccount(), apiKey);
}