API key pair restructure (#9504)

Co-authored-by: Bernardo De Marco Gonçalves <bernardomg2004@gmail.com>
This commit is contained in:
Klaus de Freitas Dornsbach 2026-03-09 10:20:17 -03:00 committed by GitHub
parent 9bbd32a8ef
commit 74af9b9875
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
91 changed files with 3349 additions and 657 deletions

View File

@ -298,8 +298,9 @@ public class EventTypes {
public static final String EVENT_REGISTER_CNI_CONFIG = "REGISTER.CNI.CONFIG";
public static final String EVENT_DELETE_CNI_CONFIG = "DELETE.CNI.CONFIG";
//register for user API and secret keys
//user API and secret keys
public static final String EVENT_REGISTER_FOR_SECRET_API_KEY = "REGISTER.USER.KEY";
public static final String EVENT_DELETE_SECRET_API_KEY = "DELETE.USER.KEY";
public static final String API_KEY_ACCESS_UPDATE = "API.KEY.ACCESS.UPDATE";
// Template Events

View File

@ -21,12 +21,13 @@ import java.util.Map;
import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.BaseCmd;
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.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import com.cloud.dc.DataCenter;
import com.cloud.domain.Domain;
@ -35,6 +36,14 @@ import com.cloud.network.vpc.VpcOffering;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering;
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
@ -97,7 +106,7 @@ public interface AccountService {
void markUserRegistered(long userId);
public String[] createApiKeyAndSecretKey(RegisterUserKeyCmd cmd);
ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd cmd);
public String[] createApiKeyAndSecretKey(final long userId);
@ -125,6 +134,8 @@ public interface AccountService {
void validateAccountHasAccessToResource(Account account, AccessType accessType, Object resource);
void validateCallingUserHasAccessToDesiredUser(Long userId);
Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly);
/**
@ -134,9 +145,15 @@ public interface AccountService {
*/
UserAccount getUserAccountById(Long userId);
public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd);
Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd);
public Pair<Boolean, Map<String, String>> getKeys(Long userId);
ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd);
List<ApiKeyPairPermission> listKeyRules(ListUserKeyRulesCmd cmd);
void deleteApiKey(DeleteUserKeysCmd cmd);
void deleteApiKey(ApiKeyPair id);
/**
* Lists user two-factor authentication provider plugins
@ -151,4 +168,13 @@ public interface AccountService {
*/
UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(final Long domainId);
ApiKeyPair getLatestUserKeyPair(Long userId);
ApiKeyPair getKeyPairById(Long id);
ApiKeyPair getKeyPairByApiKey(String apiKey);
String getAccessingApiKey(BaseCmd cmd);
List<RolePermissionEntity> getAllKeypairPermissions(String apiKey);
}

View File

@ -0,0 +1,21 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package com.cloud.user;
public enum ApiKeyPairState {
ENABLED, REMOVED, EXPIRED
}

View File

@ -65,14 +65,6 @@ public interface User extends OwnedBy, InternalIdentity {
public void setState(Account.State state);
public String getApiKey();
public void setApiKey(String apiKey);
public String getSecretKey();
public void setSecretKey(String secretKey);
public String getTimezone();
public void setTimezone(String timezone);

View File

@ -39,10 +39,6 @@ public interface UserAccount extends InternalIdentity {
String getState();
String getApiKey();
String getSecretKey();
Date getCreated();
Date getRemoved();

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.ApiKeyPairPermission;
import java.util.List;
@ -31,8 +32,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) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException;
boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException;
/**
* Verifies if the account has permission for the given list of APIs and returns only the allowed ones.
*
@ -43,4 +44,5 @@ public interface APIChecker extends Adapter {
*/
List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException;
boolean isEnabled();
List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType);
}

View File

@ -21,7 +21,7 @@ import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
public interface RolePermissionEntity extends InternalIdentity, Identity {
public enum Permission {
enum Permission {
ALLOW, DENY
}
Rule getRule();

View File

@ -104,5 +104,26 @@ public interface RoleService {
List<RolePermission> findAllPermissionsBy(Long roleId);
List<RolePermissionEntity> findAllRolePermissionsEntityBy(Long roleId, boolean considerImplicitRules);
Permission getRolePermission(String permission);
int removeRolesIfNeeded(List<? extends Role> roles);
/**
* Checks if the role of the caller account has compatible permissions of the specified role permissions.
* For each permission of the {@param rolePermissionsToAccess}, the role of the caller needs to contain the same permission.
*
* @param rolePermissions the permissions of the caller role.
* @param rolePermissionsToAccess the permissions for the role that the caller role wants to access.
* @return True if the role can be accessed with the given permissions; false otherwise.
*/
boolean roleHasPermission(Map<String, RolePermissionEntity> rolePermissions, List<RolePermissionEntity> rolePermissionsToAccess);
/**
* Given a list of role permissions, returns a {@link Map} containing the API name as the key and the {@link RolePermissionEntity} for the API as the value.
*
* @param rolePermissions Permissions for the role from role.
*/
Map<String, RolePermissionEntity> getRoleRulesAndPermissions(List<RolePermissionEntity> rolePermissions);
}

View File

@ -25,16 +25,18 @@ import org.apache.commons.lang3.StringUtils;
public final class Rule {
private final String rule;
private final Pattern matchingPattern;
private final static Pattern ALLOWED_PATTERN = Pattern.compile("^[a-zA-Z0-9*]+$");
public Rule(final String rule) {
validate(rule);
this.rule = rule;
matchingPattern = Pattern.compile(rule.toLowerCase().replace("*", "(\\w*\\*?)+"));
}
public boolean matches(final String commandName) {
return StringUtils.isNotEmpty(commandName)
&& commandName.toLowerCase().matches(rule.toLowerCase().replace("*", "\\w*"));
return StringUtils.isNotEmpty(commandName) &&
matchingPattern.matcher(commandName.toLowerCase()).matches();
}
public String getRuleString() {

View File

@ -0,0 +1,38 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.apikeypair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
import java.util.Date;
public interface ApiKeyPair extends ControlledEntity, InternalIdentity, Identity {
Long getUserId();
Date getStartDate();
Date getEndDate();
Date getCreated();
String getDescription();
String getApiKey();
String getSecretKey();
String getName();
Date getRemoved();
void setRemoved(Date date);
void validateDate();
boolean hasEndDatePassed();
}

View File

@ -0,0 +1,23 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.apikeypair;
import org.apache.cloudstack.acl.RolePermissionEntity;
public interface ApiKeyPairPermission extends RolePermissionEntity {
long getApiKeyPairId();
}

View File

@ -0,0 +1,27 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.apikeypair;
import java.util.List;
public interface ApiKeyPairService {
List<ApiKeyPairPermission> findAllPermissionsByKeyPairId(Long apiKeyPairId, Long roleId);
ApiKeyPair findByApiKey(String apiKey);
ApiKeyPair findById(Long id);
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.api;
public class ApiConstants {
public static final String ACCOUNT = "account";
public static final String ACCOUNTS = "accounts";
public static final String ACCOUNT_NAME = "accountname";
public static final String ACCOUNT_TYPE = "accounttype";
public static final String ACCOUNT_ID = "accountid";
public static final String ACCOUNT_IDS = "accountids";
@ -46,6 +47,7 @@ public class ApiConstants {
public static final String AS_NUMBER_ID = "asnumberid";
public static final String ASN_RANGE = "asnrange";
public static final String ASN_RANGE_ID = "asnrangeid";
public static final String API_KEY_FILTER = "apikeyfilter";
public static final String ASYNC_BACKUP = "asyncbackup";
public static final String AUTO_SELECT = "autoselect";
public static final String USER_API_KEY = "userapikey";
@ -356,6 +358,7 @@ public class ApiConstants {
public static final String JOB_STATUS = "jobstatus";
public static final String KEEPALIVE_ENABLED = "keepaliveenabled";
public static final String KERNEL_VERSION = "kernelversion";
public static final String KEYPAIR_ID = "keypairid";
public static final String KEY = "key";
public static final String LABEL = "label";
public static final String LASTNAME = "lastname";
@ -521,9 +524,9 @@ public class ApiConstants {
public static final String SCHEDULE = "schedule";
public static final String SCHEDULE_ID = "scheduleid";
public static final String SCOPE = "scope";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String SEARCH_BASE = "searchbase";
public static final String SECONDARY_IP = "secondaryip";
public static final String SECRET_KEY = "secretkey";
public static final String SECURITY_GROUP_IDS = "securitygroupids";
public static final String SECURITY_GROUP_NAMES = "securitygroupnames";
public static final String SECURITY_GROUP_NAME = "securitygroupname";
@ -540,6 +543,7 @@ public class ApiConstants {
public static final String SHOW_RESOURCE_ICON = "showicon";
public static final String SHOW_INACTIVE = "showinactive";
public static final String SHOW_UNIQUE = "showunique";
public static final String SHOW_PERMISSIONS = "showpermissions";
public static final String SIGNATURE = "signature";
public static final String SIGNATURE_VERSION = "signatureversion";
public static final String SINCE = "since";
@ -623,7 +627,6 @@ public class ApiConstants {
public static final String USERNAME = "username";
public static final String USER_CONFIGURABLE = "userconfigurable";
public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist";
public static final String USER_SECRET_KEY = "usersecretkey";
public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork";
public static final String USE_VIRTUAL_ROUTER_IP_RESOLVER = "userouteripresolver";
public static final String UPDATE_IN_SEQUENCE = "updateinsequence";
@ -765,6 +768,7 @@ public class ApiConstants {
public static final String ROLE_TYPE = "roletype";
public static final String ROLE_NAME = "rolename";
public static final String PERMISSION = "permission";
public static final String PERMISSIONS = "permissions";
public static final String RULE = "rule";
public static final String RULES = "rules";
public static final String RULE_ID = "ruleid";
@ -1028,7 +1032,7 @@ public class ApiConstants {
public static final String NSX_PROVIDER_PORT = "nsxproviderport";
public static final String NSX_CONTROLLER_ID = "nsxcontrollerid";
public static final String S3_ACCESS_KEY = "accesskey";
public static final String S3_SECRET_KEY = "secretkey";
public static final String SECRET_KEY = "secretkey";
public static final String S3_END_POINT = "endpoint";
public static final String S3_BUCKET_NAME = "bucket";
public static final String S3_SIGNER = "s3signer";

View File

@ -29,6 +29,7 @@ public abstract class BaseAsyncCmd extends BaseCmd {
public static final String migrationSyncObject = "migration";
public static final String snapshotHostSyncObject = "snapshothost";
public static final String gslbSyncObject = "globalserverloadbalancer";
public static final String user = "user";
private Object job;

View File

@ -36,6 +36,7 @@ import com.cloud.bgp.BGPService;
import org.apache.cloudstack.acl.ProjectRoleService;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.affinity.AffinityGroupService;
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.annotation.AnnotationService;
@ -221,6 +222,8 @@ public abstract class BaseCmd {
@Inject
public Ipv6Service ipv6Service;
@Inject
public ApiKeyPairService apiKeyPairService;
@Inject
public VnfTemplateManager vnfTemplateManager;
@Inject
public BucketApiService _bucketService;

View File

@ -24,6 +24,8 @@ import java.util.Set;
import org.apache.cloudstack.api.response.ConsoleSessionResponse;
import org.apache.cloudstack.consoleproxy.ConsoleSession;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ApiConstants.HostDetails;
@ -41,6 +43,7 @@ import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.BackupRepositoryResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.BucketResponse;
import org.apache.cloudstack.api.response.CapacityResponse;
import org.apache.cloudstack.api.response.ClusterResponse;
@ -77,6 +80,7 @@ import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse;
import org.apache.cloudstack.api.response.IpForwardingRuleResponse;
import org.apache.cloudstack.api.response.IpQuarantineResponse;
import org.apache.cloudstack.api.response.IsolationMethodResponse;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.LBHealthCheckResponse;
import org.apache.cloudstack.api.response.LBStickinessResponse;
import org.apache.cloudstack.api.response.ListResponse;
@ -583,4 +587,8 @@ public interface ResponseGenerator {
GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin);
ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView);
ApiKeyPairResponse createKeyPairResponse(ApiKeyPair keyPair);
ListResponse<BaseRolePermissionResponse> createKeypairPermissionsResponse(List<ApiKeyPairPermission> permissions);
}

View File

@ -27,7 +27,7 @@ import static org.apache.cloudstack.api.ApiConstants.S3_END_POINT;
import static org.apache.cloudstack.api.ApiConstants.S3_HTTPS_FLAG;
import static org.apache.cloudstack.api.ApiConstants.S3_MAX_ERROR_RETRY;
import static org.apache.cloudstack.api.ApiConstants.S3_SIGNER;
import static org.apache.cloudstack.api.ApiConstants.S3_SECRET_KEY;
import static org.apache.cloudstack.api.ApiConstants.SECRET_KEY;
import static org.apache.cloudstack.api.ApiConstants.S3_SOCKET_TIMEOUT;
import static org.apache.cloudstack.api.ApiConstants.S3_USE_TCP_KEEPALIVE;
import static org.apache.cloudstack.api.BaseCmd.CommandType.BOOLEAN;
@ -64,7 +64,7 @@ public final class AddImageStoreS3CMD extends BaseCmd implements ClientOptions {
@Parameter(name = S3_ACCESS_KEY, type = STRING, required = true, description = "S3 access key")
private String accessKey;
@Parameter(name = S3_SECRET_KEY, type = STRING, required = true, description = "S3 secret key")
@Parameter(name = SECRET_KEY, type = STRING, required = true, description = "S3 secret key")
private String secretKey;
@Parameter(name = S3_END_POINT, type = STRING, required = true, description = "S3 endpoint")
@ -101,7 +101,7 @@ public final class AddImageStoreS3CMD extends BaseCmd implements ClientOptions {
Map<String, String> dm = new HashMap();
dm.put(ApiConstants.S3_ACCESS_KEY, getAccessKey());
dm.put(ApiConstants.S3_SECRET_KEY, getSecretKey());
dm.put(ApiConstants.SECRET_KEY, getSecretKey());
dm.put(ApiConstants.S3_END_POINT, getEndPoint());
dm.put(ApiConstants.S3_BUCKET_NAME, getBucketName());

View File

@ -0,0 +1,81 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.user;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
@APICommand(name = "deleteUserKeys", description = "Deletes a keypair from a user", responseObject = SuccessResponse.class,
since = "4.23.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class DeleteUserKeysCmd extends BaseAsyncCmd {
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, required = true, description = "ID of the keypair to be deleted.")
private Long id;
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public long getEntityOwnerId() {
ApiKeyPair keyPair = apiKeyPairService.findById(id);
if (keyPair != null) {
return keyPair.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getId() {
return id;
}
@Override
public void execute() {
_accountService.deleteApiKey(this);
SuccessResponse response = new SuccessResponse(getCommandName());
this.setResponseObject(response);
}
@Override
public String getEventType() {
return EventTypes.EVENT_DELETE_SECRET_API_KEY;
}
@Override
public String getEventDescription() {
ApiKeyPair keyPair = apiKeyPairService.findById(id);
return String.format("Deleting API key pair with ID [%s]%s",
keyPair == null ? id : keyPair.getUuid(),
keyPair == null ? "." : String.format(" and name [%s].", keyPair.getName()));
}
@Override
public Long getSyncObjId() {
return getId();
}
}

View File

@ -32,33 +32,33 @@ import org.apache.cloudstack.api.response.UserResponse;
import java.util.Map;
@APICommand(name = "getUserKeys",
description = "This command allows the user to query the seceret and API keys for the account",
responseObject = RegisterUserKeyResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.10.0")
public class GetUserKeysCmd extends BaseCmd{
@Parameter(name= ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required")
description = "Queries the last registered secret and API keys of a user.",
responseObject = RegisterUserKeyResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.10.0")
public class GetUserKeysCmd extends BaseCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required")
private Long id;
public Long getID(){
public Long getId() {
return id;
}public long getEntityOwnerId(){
User user = _entityMgr.findById(User.class, getID());
if(user != null){
}
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getId());
if (user != null) {
return user.getAccountId();
}
else return Account.ACCOUNT_ID_SYSTEM;
return Account.ACCOUNT_ID_SYSTEM;
}
public void execute(){
public void execute() {
Pair<Boolean, Map<String, String>> keys = _accountService.getKeys(this);
RegisterUserKeyResponse response = new RegisterUserKeyResponse();
if(keys != null){
if (keys != null){
response.setApiKeyAccess(keys.first());
response.setApiKey(keys.second().get("apikey"));
response.setSecretKey(keys.second().get("secretkey"));

View File

@ -0,0 +1,68 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.user;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.ListResponse;
import java.util.List;
@APICommand(name = "listUserKeyRules",
description = "Lists the rules defined for a API key pair.",
responseObject = BaseRolePermissionResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.23.0")
public class ListUserKeyRulesCmd extends BaseCmd {
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.", required = true)
private Long id;
public Long getId() {
return id;
}
public long getEntityOwnerId() {
ApiKeyPair keyPair = apiKeyPairService.findById(getId());
if (keyPair != null) {
return keyPair.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException {
List<ApiKeyPairPermission> permissions = _accountService.listKeyRules(this);
ListResponse<BaseRolePermissionResponse> response = _responseGenerator.createKeypairPermissionsResponse(permissions);
response.setResponseName(getCommandName());
setResponseObject(response);
}
}

View File

@ -0,0 +1,101 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.user;
import com.cloud.user.Account;
import com.cloud.user.UserAccount;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.UserResponse;
@APICommand(name = "listUserKeys",
description = "Lists the API key pairs (API and secret keys) of a user.",
responseObject = ApiKeyPairResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = true,
authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin},
since = "4.23.0")
public class ListUserKeysCmd extends BaseListCmd {
@ACL
@Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "ID of the user that owns the keys.")
private Long userId;
@ACL
@Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.")
private Long keyPairId;
@Parameter(name = ApiConstants.API_KEY_FILTER, type = CommandType.STRING, description = "API key of the key pair.")
private String apiKeyFilter;
@Parameter(name = ApiConstants.SHOW_PERMISSIONS, type = CommandType.BOOLEAN, description = "Whether API Key rules should be returned. Defaults to false.")
private boolean showPermissions = false;
@Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin},
description = "Lists all API key pairs of users that are accessible by the calling account. Only available for administrators. Defaults to false.")
private boolean listAll = false;
public Long getUserId() {
return userId;
}
public Long getKeyId() {
return keyPairId;
}
public String getApiKeyFilter() {
return apiKeyFilter;
}
public Boolean getShowPermissions() {
return showPermissions;
}
public boolean getListAll() {
return listAll;
}
public long getEntityOwnerId() {
if (getKeyId() != null) {
ApiKeyPair keypair = apiKeyPairService.findById(getKeyId());
if (keypair != null) {
return keypair.getAccountId();
}
} else if (getUserId() != null) {
UserAccount userAccount = _accountService.getUserAccountById(getUserId());
if (userAccount != null) {
return userAccount.getAccountId();
}
}
return Account.ACCOUNT_ID_SYSTEM;
}
public void execute() {
ListResponse<ApiKeyPairResponse> finalResponse = _accountService.listKeys(this);
finalResponse.setObjectName("userkeys");
finalResponse.setResponseName(getCommandName());
setResponseObject(finalResponse);
}
}

View File

@ -1,93 +0,0 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.user;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.RegisterUserKeyResponse;
import org.apache.cloudstack.api.response.UserResponse;
import com.cloud.user.Account;
import com.cloud.user.User;
@APICommand(name = "registerUserKeys",
responseObject = RegisterUserKeyResponse.class,
description = "This command allows a user to register for the developer API, returning a secret key and an API key. This request is made through the integration API port, so it is a privileged command and must be made on behalf of a user. It is up to the implementer just how the username and password are entered, and then how that translates to an integration API request. Both secret key and API key should be returned to the user",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
public class RegisterUserKeyCmd extends BaseCmd {
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "User id")
private Long id;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getId());
if (user != null) {
return user.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}
@Override
public Long getApiResourceId() {
return id;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public void execute() {
String[] keys = _accountService.createApiKeyAndSecretKey(this);
RegisterUserKeyResponse response = new RegisterUserKeyResponse();
if (keys != null) {
response.setApiKey(keys[0]);
response.setSecretKey(keys[1]);
}
response.setObjectName("userkeys");
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
}

View File

@ -0,0 +1,209 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.command.admin.user;
import com.cloud.event.EventTypes;
import com.cloud.user.Account;
import com.cloud.user.User;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.commons.lang3.StringUtils;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.UserResponse;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@APICommand(name = "registerUserKeys",
responseObject = ApiKeyPairResponse.class,
description = "Registers an API key pair (API and secret keys) for a user.",
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
public class RegisterUserKeysCmd extends BaseAsyncCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user.")
private Long id;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "API key pair name.")
private String name;
@Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "API key pair description.")
private String description;
@Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the API key pair. " +
ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS)
private Date startDate;
@Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Expiration date of the API key pair. " +
ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS)
private Date endDate;
@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, description = "The rules of the API key pair. If no rules are informed, " +
"defaults to allowing all account permissions. Otherwise, only the explicitly informed permissions for the key pair will be " +
"considered. Lower indexed rules take precedence over higher. Thus, in the following example: " +
"\"rules[0].rule=deleteUserKeys rules[0].permission=deny rules[1].rule=*UserKey* rules[1].permission=allow\", all rules matching " +
"the expression \"*UserKeys*\" will be allowed, except for \"deleteUserKeys\".")
private Map rules;
public void setUserId(Long userId) {
this.id = userId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public void setRules(Map rules) {
this.rules = rules;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
public List<Map<String, Object>> getRules() {
List<Map<String, Object>> rulesDetails = new ArrayList<>();
if (rules == null) {
return rulesDetails;
}
for (Object ruleObject : rules.values()) {
HashMap<String, String> detail = (HashMap<String, String>) ruleObject;
Map<String, Object> ruleDetails = new HashMap<>();
String rule = detail.get(ApiConstants.RULE);
if (StringUtils.isEmpty(rule)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Empty rule has been provided in the [rules] parameter.");
}
String permission = detail.get(ApiConstants.PERMISSION);
if (StringUtils.isEmpty(permission)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Rule [%s] has no permission associated with it," +
" please specify if it is either [allow] or [deny].", rule));
}
ruleDetails.put(ApiConstants.RULE, new Rule(rule));
ruleDetails.put(ApiConstants.PERMISSION, roleService.getRolePermission(permission));
String description = detail.get(ApiConstants.DESCRIPTION);
if (StringUtils.isNotEmpty(description)) {
ruleDetails.put(ApiConstants.DESCRIPTION, description);
}
rulesDetails.add(ruleDetails);
}
return rulesDetails;
}
@Override
public long getEntityOwnerId() {
User user = _entityMgr.findById(User.class, getUserId());
List<Long> accessibleUsers = _queryService.searchForAccessibleUsers();
if (user != null && accessibleUsers.stream().anyMatch(u -> u == user.getId())) {
return user.getAccountId();
}
return Account.ACCOUNT_ID_SYSTEM;
}
public Long getUserId() {
return id;
}
@Override
public Long getApiResourceId() {
User user = _entityMgr.findById(User.class, getUserId());
if (user != null) {
return user.getId();
}
return null;
}
@Override
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.User;
}
@Override
public void execute() {
ApiKeyPair apiKeyPair = _accountService.createApiKeyAndSecretKey(this);
ApiKeyPairResponse response = new ApiKeyPairResponse();
if (apiKeyPair != null) {
response = _responseGenerator.createKeyPairResponse(apiKeyPair);
}
response.setObjectName("userkeys");
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
@Override
public String getEventType() {
return EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY;
}
@Override
public String getEventDescription() {
String userUuid = getResourceUuid(ApiConstants.ID);
return String.format("Registering API keypair for user [%s].", userUuid == null ? id : userUuid);
}
@Override
public String getSyncObjType() {
return BaseAsyncCmd.user;
}
@Override
public Long getSyncObjId() {
return getUserId();
}
}

View File

@ -46,8 +46,8 @@ public class UpdateUserCmd extends BaseCmd {
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "The API key for the user. Must be specified with userSecretKey")
private String apiKey;
@Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "Updates the latest API key of the user. Must be specified with usersecretkey")
private String userApiKey;
@Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "Email")
private String email;
@ -70,8 +70,8 @@ public class UpdateUserCmd extends BaseCmd {
@Parameter(name = ApiConstants.CURRENT_PASSWORD, type = CommandType.STRING, description = "Current password that was being used by the user. You must inform the current password when updating the password.", acceptedOnAdminPort = false)
private String currentPassword;
@Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey")
private String secretKey;
@Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "Updates the latest secret key of the user. Must be specified with userapikey.")
private String userSecretKey;
@Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "Determines if Api key access for this user is enabled, disabled or inherits the value from its parent, the owning account", since = "4.20.1.0", authorized = {RoleType.Admin})
private String apiKeyAccess;
@ -99,7 +99,7 @@ public class UpdateUserCmd extends BaseCmd {
/////////////////////////////////////////////////////
public String getApiKey() {
return apiKey;
return userApiKey;
}
public String getEmail() {
@ -127,7 +127,7 @@ public class UpdateUserCmd extends BaseCmd {
}
public String getSecretKey() {
return secretKey;
return userSecretKey;
}
public String getApiKeyAccess() {

View File

@ -0,0 +1,285 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.api.response;
import com.cloud.user.ApiKeyPairState;
import com.google.gson.annotations.SerializedName;
import java.util.Date;
import java.util.List;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.api.ApiConstants;
import com.cloud.serializer.Param;
import org.apache.cloudstack.api.BaseResponseWithAnnotations;
import org.apache.cloudstack.api.EntityReference;
@EntityReference(value = ApiKeyPair.class)
public class ApiKeyPairResponse extends BaseResponseWithAnnotations {
@SerializedName(ApiConstants.NAME)
@Param(description = "Name of the API key pair")
private String name;
@SerializedName(ApiConstants.API_KEY)
@Param(description = "The API key of the registered user.", isSensitive = true)
private String userApiKey;
@SerializedName(ApiConstants.SECRET_KEY)
@Param(description = "The secret key of the registered user.", isSensitive = true)
private String userSecretKey;
@SerializedName(ApiConstants.USER_ID)
@Param(description = "ID of the user that owns the keypair.")
private String userId;
@SerializedName(ApiConstants.USERNAME)
@Param(description = "Username of the keypair's owner.")
private String username;
@SerializedName(ApiConstants.ID)
@Param(description = "ID of the API key pair.", isSensitive = true)
private String id;
@SerializedName(ApiConstants.DESCRIPTION)
@Param(description = "API key pair description.")
private String description;
@SerializedName(ApiConstants.START_DATE)
@Param(description = "API key pair start date.")
private Date startDate;
@SerializedName(ApiConstants.END_DATE)
@Param(description = "API key pair expiration date.")
private Date endDate;
@SerializedName(ApiConstants.CREATED)
@Param(description = "API key pair creation timestamp.")
private Date created;
@SerializedName(ApiConstants.ACCOUNT_TYPE)
@Param(description = "Account type.")
private String accountType;
@SerializedName(ApiConstants.ACCOUNT_ID)
@Param(description = "Account ID.")
private String accountId;
@SerializedName(ApiConstants.ACCOUNT_NAME)
@Param(description = "Account name.")
private String accountName;
@SerializedName(ApiConstants.ROLE_ID)
@Param(description = "ID of the role.")
private String roleId;
@SerializedName(ApiConstants.ROLE_TYPE)
@Param(description = "Type of the role (Admin, ResourceAdmin, DomainAdmin, User).")
private String roleType;
@SerializedName(ApiConstants.ROLE_NAME)
@Param(description = "Name of the role.")
private String roleName;
@SerializedName(ApiConstants.PERMISSIONS)
@Param(description = "Permissions of the API key pair.")
private List<BaseRolePermissionResponse> permissions;
@SerializedName(ApiConstants.DOMAIN_ID)
@Param(description = "ID of the domain which the account belongs to.")
private String domainId;
@SerializedName(ApiConstants.DOMAIN)
@Param(description = "Name of the domain which the account belongs to.")
private String domainName;
@SerializedName(ApiConstants.DOMAIN_PATH)
@Param(description = "Path of the domain which the account belongs to.")
private String domainPath;
@SerializedName(ApiConstants.STATE)
@Param(description = "State of the API key pair.")
private ApiKeyPairState state;
public String getApiKey() {
return userApiKey;
}
public void setApiKey(String apiKey) {
this.userApiKey = apiKey;
}
public String getSecretKey() {
return userSecretKey;
}
public void setSecretKey(String secretKey) {
this.userSecretKey = secretKey;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public Date getCreated() {
return created;
}
public void setCreated(Date created) {
this.created = created;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public String getRoleId() {
return roleId;
}
public void setRoleId(String roleId) {
this.roleId = roleId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRoleType() {
return roleType;
}
public void setRoleType(String roleType) {
this.roleType = roleType;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getDomainId() {
return domainId;
}
public void setDomainId(String domainId) {
this.domainId = domainId;
}
public String getDomainName() {
return domainName;
}
public void setDomainName(String domainName) {
this.domainName = domainName;
}
public String getDomainPath() {
return domainPath;
}
public void setDomainPath(String domainPath) {
this.domainPath = domainPath;
}
public ApiKeyPairState getState() {
return state;
}
public void setState(ApiKeyPairState state) {
this.state = state;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public List<BaseRolePermissionResponse> getPermissions() {
return permissions;
}
public void setPermissions(List<BaseRolePermissionResponse> permissions) {
this.permissions = permissions;
}
}

View File

@ -95,12 +95,12 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons
@Param(description = "The timezone user was created in")
private String timezone;
@SerializedName("apikey")
@SerializedName(ApiConstants.API_KEY)
@Param(description = "The API key of the user", isSensitive = true)
private String apiKey;
@Deprecated
@SerializedName("secretkey")
@SerializedName(ApiConstants.SECRET_KEY)
@Param(description = "The secret key of the user", isSensitive = true)
private String secretKey;

View File

@ -145,6 +145,8 @@ public interface QueryService {
ListResponse<UserResponse> searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException;
List<Long> searchForAccessibleUsers();
ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd);
ListResponse<ResourceTagResponse> listTags(ListTagsCmd cmd);

View File

@ -17,13 +17,46 @@
package org.apache.cloudstack.acl;
import com.cloud.exception.InvalidParameterValueException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.cloudstack.api.APICommand;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
public class RuleTest {
private static List<String> apiNames;
private static List<Rule> apiRules;
private static ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
@BeforeClass
public static void setup() {
provider.addIncludeFilter(new AnnotationTypeFilter(APICommand.class));
Set<BeanDefinition> beanDefinitions = provider.findCandidateComponents("org.apache.cloudstack.api");
apiNames = new ArrayList<>();
apiRules = new ArrayList<>();
for(BeanDefinition bd : beanDefinitions) {
if (bd instanceof AnnotatedBeanDefinition) {
Map<String, Object> annotationAttributeMap = ((AnnotatedBeanDefinition) bd).getMetadata()
.getAnnotationAttributes(APICommand.class.getName());
String apiName = annotationAttributeMap.get("name").toString();
apiNames.add(apiName);
apiRules.add(new Rule(apiName));
}
}
}
@Test
public void testToString() throws Exception {
Rule rule = new Rule("someString");
@ -31,21 +64,89 @@ public class RuleTest {
}
@Test
public void testMatchesEmpty() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(""));
public void ruleMatchesTestNoMatchesOnEmptyString() throws Exception {
String testCmd = "";
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(testCmd)) {
matches.add(rule.getRuleString());
}
}
Assert.assertEquals(matches.size(), 0);
}
@Test
public void testMatchesNull() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(null));
public void ruleMatchesTestNoMatchesOnNull() throws Exception {
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(null)) {
matches.add(rule.getRuleString());
}
}
Assert.assertTrue(matches.isEmpty());
}
@Test
public void testMatchesSpace() throws Exception {
Rule rule = new Rule("someString");
Assert.assertFalse(rule.matches(" "));
public void ruleMatchesTestNoMatchesOnSpaceCharacter() throws Exception {
String testCmd = " ";
List<String> matches = new ArrayList<>();
for (Rule rule : apiRules) {
if (rule.matches(testCmd)) {
matches.add(rule.getRuleString());
}
}
Assert.assertTrue(matches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnEndWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile("list.*");
Rule acsRegexRule = new Rule("list*");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnMiddleWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile("list.*s");
Rule acsRegexRule = new Rule("list*s");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
public void ruleMatchesTestWildCardOnStartWorksAsNormalRegex() {
setup();
Pattern regexPattern = Pattern.compile(".*User");
Rule acsRegexRule = new Rule("*User");
List<String> nonMatches = new ArrayList<>();
for (String apiName : apiNames) {
if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) {
nonMatches.add(apiName);
}
}
Assert.assertTrue(nonMatches.isEmpty());
}
@Test
@ -73,7 +174,25 @@ public class RuleTest {
}
@Test
public void testValidateRuleWithValidData() throws Exception {
public void ruleMatchesTestWildcardOnRuleAndCommand() throws Exception {
Rule rule = new Rule("*");
Assert.assertTrue(rule.matches("list*"));
}
@Test
public void ruleMatchesTestWildcardOnRuleAndCommandNotAllowed() throws Exception {
Rule rule = new Rule("list*");
Assert.assertFalse(rule.matches("*"));
}
@Test
public void ruleMatchesTestWithMultipleStars() throws Exception {
Rule rule = new Rule("list***");
Assert.assertFalse(rule.matches("api"));
}
@Test
public void testRuleToStringWithValidStrings() throws Exception {
for (String rule : Arrays.asList("a", "1", "someApi", "someApi321", "123SomeApi",
"prefix*", "*middle*", "*Suffix",
"*", "**", "f***", "m0nk3yMa**g1c*")) {
@ -82,7 +201,7 @@ public class RuleTest {
}
@Test
public void testValidateRuleWithInvalidData() throws Exception {
public void testRuleToStringWithInvalidStrings() throws Exception {
for (String rule : Arrays.asList(null, "", " ", " ", "\n", "\t", "\r", "\"", "\'",
"^someApi$", "^someApi", "some$", "some-Api;", "some,Api",
"^", "$", "^$", ".*", "\\w+", "r**l3rd0@Kr3", "j@s1n|+|0ȷ",

View File

@ -1947,7 +1947,7 @@ public class Upgrade410to420 extends DbUpgradeAbstractImpl {
Map<String, String> detailMap = new HashMap<String, String>();
detailMap.put(ApiConstants.S3_ACCESS_KEY, s3_accesskey);
detailMap.put(ApiConstants.S3_SECRET_KEY, s3_secretkey);
detailMap.put(ApiConstants.SECRET_KEY, s3_secretkey);
detailMap.put(ApiConstants.S3_BUCKET_NAME, s3_bucket);
detailMap.put(ApiConstants.S3_END_POINT, s3_endpoint);
detailMap.put(ApiConstants.S3_HTTPS_FLAG, String.valueOf(s3_https));

View File

@ -36,7 +36,6 @@ import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
@Entity
@ -69,13 +68,6 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
@Column(name = "state")
private String state;
@Column(name = "api_key")
private String apiKey = null;
@Encrypt
@Column(name = "secret_key")
private String secretKey = null;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@ -203,24 +195,6 @@ public class UserAccountVO implements UserAccount, InternalIdentity {
this.state = state;
}
@Override
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public Date getCreated() {
return created;

View File

@ -33,7 +33,6 @@ import org.apache.cloudstack.api.InternalIdentity;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import com.cloud.user.Account.State;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
import org.apache.commons.lang3.StringUtils;
@ -71,13 +70,6 @@ public class UserVO implements User, Identity, InternalIdentity {
@Enumerated(value = EnumType.STRING)
private State state;
@Column(name = "api_key")
private String apiKey = null;
@Encrypt
@Column(name = "secret_key")
private String secretKey = null;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@ -150,8 +142,6 @@ public class UserVO implements User, Identity, InternalIdentity {
this.setTimezone(user.getTimezone());
this.setUuid(user.getUuid());
this.setSource(user.getSource());
this.setApiKey(user.getApiKey());
this.setSecretKey(user.getSecretKey());
this.setExternalEntity(user.getExternalEntity());
this.setRegistered(user.isRegistered());
this.setRegistrationToken(user.getRegistrationToken());
@ -243,26 +233,6 @@ public class UserVO implements User, Identity, InternalIdentity {
this.state = state;
}
@Override
public String getApiKey() {
return apiKey;
}
@Override
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
@Override
public String getSecretKey() {
return secretKey;
}
@Override
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
@Override
public String getTimezone() {
if (StringUtils.isEmpty(timezone)) {

View File

@ -30,6 +30,4 @@ public interface UserAccountDao extends GenericDao<UserAccountVO, Long> {
List<UserAccountVO> getUserAccountByEmail(String email, Long domainId);
boolean validateUsernameInDomain(String username, Long domainId);
UserAccount getUserByApiKey(String apiKey);
}

View File

@ -19,7 +19,6 @@ package com.cloud.user.dao;
import com.cloud.user.UserAccount;
import com.cloud.user.UserAccountVO;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.springframework.stereotype.Component;
@ -28,14 +27,6 @@ import java.util.List;
@Component
public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> implements UserAccountDao {
protected final SearchBuilder<UserAccountVO> userAccountSearch;
public UserAccountDaoImpl() {
userAccountSearch = createSearchBuilder();
userAccountSearch.and("apiKey", userAccountSearch.entity().getApiKey(), SearchCriteria.Op.EQ);
userAccountSearch.done();
}
@Override
public List<UserAccountVO> getAllUsersByNameAndEntity(String username, String entity) {
if (username == null) {
@ -79,12 +70,4 @@ public class UserAccountDaoImpl extends GenericDaoBase<UserAccountVO, Long> impl
}
return false;
}
@Override
public UserAccount getUserByApiKey(String apiKey) {
SearchCriteria<UserAccountVO> sc = userAccountSearch.create();
sc.setParameters("apiKey", apiKey);
return findOneBy(sc);
}
}

View File

@ -37,13 +37,6 @@ public interface UserDao extends GenericDao<UserVO, Long> {
List<UserVO> listByAccount(long accountId);
/**
* Finds a user based on the secret key provided.
* @param secretKey
* @return
*/
UserVO findUserBySecretKey(String secretKey);
/**
* Finds a user based on the registration token provided.
* @param registrationToken

View File

@ -65,10 +65,6 @@ public class UserDaoImpl extends GenericDaoBase<UserVO, Long> implements UserDao
UserIdSearch.and("id", UserIdSearch.entity().getId(), SearchCriteria.Op.EQ);
UserIdSearch.done();
SecretKeySearch = createSearchBuilder();
SecretKeySearch.and("secretKey", SecretKeySearch.entity().getSecretKey(), SearchCriteria.Op.EQ);
SecretKeySearch.done();
RegistrationTokenSearch = createSearchBuilder();
RegistrationTokenSearch.and("registrationToken", RegistrationTokenSearch.entity().getRegistrationToken(), SearchCriteria.Op.EQ);
RegistrationTokenSearch.done();
@ -121,13 +117,6 @@ public class UserDaoImpl extends GenericDaoBase<UserVO, Long> implements UserDao
return listBy(sc);
}
@Override
public UserVO findUserBySecretKey(String secretKey) {
SearchCriteria<UserVO> sc = SecretKeySearch.create();
sc.setParameters("secretKey", secretKey);
return findOneBy(sc);
}
@Override
public UserVO findUserByRegistrationToken(String registrationToken) {
SearchCriteria<UserVO> sc = RegistrationTokenSearch.create();

View File

@ -0,0 +1,61 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "api_keypair_permissions")
public class ApiKeyPairPermissionVO extends RolePermissionBaseVO implements ApiKeyPairPermission {
@Column(name = "api_keypair_id")
private long apiKeyPairId;
@Column(name = "sort_order")
private long sortOrder = 0;
public ApiKeyPairPermissionVO(long apiKeyPairId, String rule, Permission permission, String description) {
super(rule, permission, description);
this.apiKeyPairId = apiKeyPairId;
}
public ApiKeyPairPermissionVO(String rule, Permission permission, String description) {
super(rule, permission, description);
}
public ApiKeyPairPermissionVO() {
}
public long getApiKeyPairId() {
return this.apiKeyPairId;
}
public void setApiKeyPairId(long keyPairId) {
this.apiKeyPairId = keyPairId;
}
public void setSortOrder(long sortOrder) {
this.sortOrder = sortOrder;
}
public long getSortOrder() {
return sortOrder;
}
}

View File

@ -0,0 +1,244 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.user.Account;
import com.cloud.utils.db.Encrypt;
import java.time.Instant;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import org.joda.time.DateTime;
@Entity
@Table(name = "api_keypair")
public class ApiKeyPairVO implements ApiKeyPair {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Column(name = "uuid", nullable = false)
private String uuid = UUID.randomUUID().toString();
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "domain_id", nullable = false)
private Long domainId;
@Column(name = "account_id", nullable = false)
private Long accountId;
@Column(name = "start_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date startDate;
@Column(name = "end_date")
@Temporal(value = TemporalType.TIMESTAMP)
private Date endDate;
@Column(name = "created", nullable = false)
@Temporal(value = TemporalType.TIMESTAMP)
private Date created = Date.from(Instant.now());
@Column(name = "description")
private String description = "";
@Column(name = "api_key", nullable = false)
private String apiKey;
@Encrypt
@Column(name = "secret_key", nullable = false)
private String secretKey;
@Column(name = "removed")
@Temporal(value = TemporalType.TIMESTAMP)
private Date removed;
public ApiKeyPairVO() {
}
public ApiKeyPairVO(Long id) {
this.id = id;
}
public ApiKeyPairVO(Long userId, String description, Date startDate, Date endDate,
String apiKey, String secretKey) {
this.userId = userId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.apiKey = apiKey;
this.secretKey = secretKey;
}
public ApiKeyPairVO(String name, Long userId, String description, Date startDate, Date endDate, Account account) {
this.name = Objects.requireNonNullElseGet(name, () -> userId + " - API key pair");
this.userId = userId;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
this.domainId = account.getDomainId();
this.accountId = account.getAccountId();
}
public ApiKeyPairVO(Long id, Long userId) {
this.id = id;
this.userId = userId;
}
public void validateDate() {
Date now = DateTime.now().toDate();
Date keypairStart = this.getStartDate();
Date keypairExpiration = this.getEndDate();
if (keypairStart != null && now.compareTo(keypairStart) <= 0) {
throw new InvalidParameterValueException(String.format("API key pair is not valid yet, start date: %s", keypairStart));
}
if (keypairExpiration != null && now.compareTo(keypairExpiration) >= 0) {
throw new InvalidParameterValueException(String.format("API key pair is expired, expiration date: %s", keypairExpiration));
}
}
public boolean hasEndDatePassed() {
Date now = DateTime.now().toDate();
Date keypairExpiration = this.getEndDate();
return keypairExpiration != null && now.compareTo(keypairExpiration) >= 0;
}
public long getId() {
return id;
}
public String getUuid() {
return uuid;
}
public Long getUserId() {
return userId;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
public Date getCreated() {
return created;
}
public String getDescription() {
return description;
}
public String getApiKey() {
return apiKey;
}
public String getSecretKey() {
return secretKey;
}
public Class<?> getEntityType() {
return ApiKeyPair.class;
}
public String getName() {
return name;
}
public Date getRemoved() {
return removed;
}
@Override
public long getDomainId() {
return this.domainId;
}
@Override
public long getAccountId() {
return this.accountId;
}
public void setId(Long id) { this.id = id; }
public void setUuid(String uuid) {
this.uuid = uuid;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public void setDescription(String description) {
this.description = description;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public void setName(String name) {
this.name = name;
}
public void setDomainId(Long domainId) {
this.domainId = domainId;
}
public void setAccountId(Long accountId) {
this.accountId = accountId;
}
public void setCreated(Date created) {
this.created = created;
}
public void setRemoved(Date removed) {
this.removed = removed;
}
}

View File

@ -50,6 +50,12 @@ public class RolePermissionBaseVO implements RolePermissionEntity {
public RolePermissionBaseVO() { this.uuid = UUID.randomUUID().toString(); }
public RolePermissionBaseVO(final String rule, final Permission permission) {
this();
this.rule = rule;
this.permission = permission;
}
public RolePermissionBaseVO(final String rule, final Permission permission, final String description) {
this();
this.rule = rule;

View File

@ -0,0 +1,38 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.Pair;
import java.util.List;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
public interface ApiKeyPairDao extends GenericDao<ApiKeyPairVO, Long> {
ApiKeyPairVO findBySecretKey(String secretKey);
ApiKeyPairVO findByApiKey(String apiKey);
ApiKeyPairVO findByUuid(String uuid);
Pair<List<ApiKeyPairVO>, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId);
ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId);
Pair<List<ApiKeyPairVO>, Integer> listByUserIdsPaginated(List<Long> userIds, ListUserKeysCmd cmd);
}

View File

@ -0,0 +1,92 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.util.List;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Component;
@Component
public class ApiKeyPairDaoImpl extends GenericDaoBase<ApiKeyPairVO, Long> implements ApiKeyPairDao {
private static final String ID = "id";
private static final String USER_ID = "userId";
private static final String API_KEY = "apiKey";
private static final String SECRET_KEY = "secretKey";
private final SearchBuilder<ApiKeyPairVO> keyPairSearch;
ApiKeyPairDaoImpl() {
super();
keyPairSearch = createSearchBuilder();
keyPairSearch.and(API_KEY, keyPairSearch.entity().getApiKey(), SearchCriteria.Op.EQ);
keyPairSearch.and(SECRET_KEY, keyPairSearch.entity().getSecretKey(), SearchCriteria.Op.EQ);
keyPairSearch.and(ID, keyPairSearch.entity().getId(), SearchCriteria.Op.EQ);
keyPairSearch.and(USER_ID, keyPairSearch.entity().getUserId(), SearchCriteria.Op.IN);
keyPairSearch.done();
}
@Override
public ApiKeyPairVO findByApiKey(String apiKey) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(API_KEY, apiKey);
return findOneBy(sc);
}
public ApiKeyPairVO findBySecretKey(String secretKey) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(SECRET_KEY, secretKey);
return findOneBy(sc);
}
public Pair<List<ApiKeyPairVO>, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId) {
SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParametersIfNotNull(USER_ID, userId);
sc.setParametersIfNotNull(ID, apiKeyId);
final Filter searchFilter = new Filter(100);
return searchAndCount(sc, searchFilter);
}
public ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId) {
final SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParametersIfNotNull(USER_ID, userId);
final Filter searchBySorted = new Filter(ApiKeyPairVO.class, ID, false, null, null);
return findOneBy(sc, searchBySorted);
}
public Pair<List<ApiKeyPairVO>, Integer> listByUserIdsPaginated(List<Long> userIds, ListUserKeysCmd cmd) {
Long pageSizeVal = cmd.getPageSizeVal();
Long startIndex = cmd.getStartIndex();
Filter searchFilter = new Filter(ApiKeyPairVO.class, ID, true, startIndex, pageSizeVal);
final SearchCriteria<ApiKeyPairVO> sc = keyPairSearch.create();
sc.setParameters(USER_ID, (Object[]) userIds.toArray(new Long[0]));
Pair<List<ApiKeyPairVO>, Integer> apiKeyPairVOList = searchAndCount(sc, searchFilter);
if (CollectionUtils.isEmpty(apiKeyPairVOList.first())) {
return new Pair<>(List.of(), 0);
}
return apiKeyPairVOList;
}
}

View File

@ -0,0 +1,28 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import java.util.List;
public interface ApiKeyPairPermissionsDao extends GenericDao<ApiKeyPairPermissionVO, Long> {
List<ApiKeyPairPermissionVO> findAllByApiKeyPairId(Long apiKeyPairId);
List<ApiKeyPairPermissionVO> findAllByKeyPairIdSorted(Long apiKeyPairId);
}

View File

@ -0,0 +1,71 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl.dao;
import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDaoBase;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import java.util.Collections;
import java.util.Objects;
import org.apache.commons.collections.CollectionUtils;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ApiKeyPairPermissionsDaoImpl extends GenericDaoBase<ApiKeyPairPermissionVO, Long> implements ApiKeyPairPermissionsDao {
private static final String API_KEY_PAIR_ID = "apiKeyPairId";
private static final String SORT_ORDER = "sortOrder";
private final SearchBuilder<ApiKeyPairPermissionVO> permissionByApiKeyPairIdSearch;
public ApiKeyPairPermissionsDaoImpl() {
super();
permissionByApiKeyPairIdSearch = createSearchBuilder();
permissionByApiKeyPairIdSearch.and(API_KEY_PAIR_ID, permissionByApiKeyPairIdSearch.entity().getApiKeyPairId(), SearchCriteria.Op.EQ);
permissionByApiKeyPairIdSearch.done();
}
public List<ApiKeyPairPermissionVO> findAllByApiKeyPairId(Long apiKeyPairId) {
SearchCriteria<ApiKeyPairPermissionVO> sc = permissionByApiKeyPairIdSearch.create();
sc.setParameters(API_KEY_PAIR_ID, String.valueOf(apiKeyPairId));
return listBy(sc);
}
@Override
public ApiKeyPairPermissionVO persist(final ApiKeyPairPermissionVO item) {
item.setSortOrder(0);
final List<ApiKeyPairPermissionVO> permissionsList = findAllByKeyPairIdSorted(item.getApiKeyPairId());
if (!CollectionUtils.isEmpty(permissionsList)) {
ApiKeyPairPermissionVO lastPermission = permissionsList.get(permissionsList.size() - 1);
item.setSortOrder(lastPermission.getSortOrder() + 1);
}
return super.persist(item);
}
@Override
public List<ApiKeyPairPermissionVO> findAllByKeyPairIdSorted(Long apiKeyPairId) {
final SearchCriteria<ApiKeyPairPermissionVO> sc = permissionByApiKeyPairIdSearch.create();
sc.setParameters(API_KEY_PAIR_ID, apiKeyPairId);
final Filter searchBySorted = new Filter(ApiKeyPairPermissionVO.class, SORT_ORDER, true, null, null);
final List<ApiKeyPairPermissionVO> apiKeyPairPermissionList = listBy(sc, searchBySorted);
return Objects.requireNonNullElse(apiKeyPairPermissionList, Collections.emptyList());
}
}

View File

@ -77,7 +77,7 @@ public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase<ImageStoreD
for (ImageStoreDetailVO detail : details) {
String name = detail.getName();
String value = detail.getValue();
if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.S3_SECRET_KEY)) {
if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.SECRET_KEY)) {
value = DBEncryptionUtil.decrypt(value);
}
detailsMap.put(name, value);

View File

@ -310,4 +310,6 @@
<bean id="gpuDeviceDaoImpl" class="com.cloud.gpu.dao.GpuDeviceDaoImpl" />
<bean id="vgpuProfileDaoImpl" class="com.cloud.gpu.dao.VgpuProfileDaoImpl" />
<bean id="importVMTaskDaoImpl" class="com.cloud.vm.dao.ImportVMTaskDaoImpl" />
<bean id="apiKeyPairDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairDaoImpl" />
<bean id="apiKeyPairPermissionsDaoImpl" class="org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDaoImpl" />
</beans>

View File

@ -49,3 +49,52 @@ CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` (
INDEX `i_webhook_filter__webhook_id`(`webhook_id`),
CONSTRAINT `fk_webhook_filter__webhook_id` FOREIGN KEY(`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- "api_keypair" table for API and secret keys
CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`uuid` varchar(40) UNIQUE NOT NULL,
`name` varchar(255) NOT NULL,
`domain_id` bigint(20) unsigned NOT NULL,
`account_id` bigint(20) unsigned NOT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`start_date` datetime,
`end_date` datetime,
`description` varchar(100),
`api_key` varchar(255) NOT NULL,
`secret_key` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`removed` datetime,
PRIMARY KEY (`id`),
CONSTRAINT `fk_api_keypair__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`),
CONSTRAINT `fk_api_keypair__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account`(`id`),
CONSTRAINT `fk_api_keypair__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `cloud`.`domain`(`id`)
);
-- "api_keypair_permissions" table for API key pairs permissions
CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair_permissions` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`uuid` varchar(40) UNIQUE,
`sort_order` bigint(20) unsigned NOT NULL DEFAULT 0,
`rule` varchar(255) NOT NULL,
`api_keypair_id` bigint(20) unsigned NOT NULL,
`permission` varchar(255) NOT NULL,
`description` varchar(255),
PRIMARY KEY (`id`),
CONSTRAINT `fk_keypair_permissions__api_keypair_id` FOREIGN KEY(`api_keypair_id`) REFERENCES `cloud`.`api_keypair`(`id`)
);
-- Populate "api_keypair" table with existing user API keys
INSERT INTO `cloud`.`api_keypair` (uuid, user_id, domain_id, account_id, api_key, secret_key, created, name)
SELECT UUID(), user.id, account.domain_id, account.id, user.api_key, user.secret_key, NOW(), 'Active key pair'
FROM `cloud`.`user` AS user
JOIN `cloud`.`account` AS account ON user.account_id = account.id
WHERE user.api_key IS NOT NULL AND user.secret_key IS NOT NULL;
-- Drop API keys from user table
ALTER TABLE `cloud`.`user` DROP COLUMN api_key, DROP COLUMN secret_key;
-- Grant access to the "deleteUserKeys" API to the "User", "Domain Admin" and "Resource Admin" roles, similarly to the "registerUserKeys" API
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'deleteUserKeys', 'ALLOW');
CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKeys', 'ALLOW');

View File

@ -29,8 +29,6 @@ select
user.lastname,
user.email,
user.state,
user.api_key,
user.secret_key,
user.created,
user.removed,
user.timezone,

View File

@ -131,7 +131,7 @@ public class ImageStoreHelper {
String key = keyIter.next().toString();
String value = details.get(key);
// encrypt swift key or s3 secret key
if (key.equals(ApiConstants.KEY) || key.equals(ApiConstants.S3_SECRET_KEY)) {
if (key.equals(ApiConstants.KEY) || key.equals(ApiConstants.SECRET_KEY)) {
value = DBEncryptionUtil.encrypt(value);
}
ImageStoreDetailVO detail = new ImageStoreDetailVO(store.getId(), key, value, true);

View File

@ -17,15 +17,18 @@
package org.apache.cloudstack.acl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.utils.cache.LazyCache;
@ -47,7 +50,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
private RoleService roleService;
private List<PluggableService> services;
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<RoleType, Set<String>>();
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<>();
private LazyCache<Long, Account> accountCache;
private LazyCache<Long, Pair<Role, List<RolePermission>>> rolePermissionsCache;
@ -56,7 +59,7 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
protected DynamicRoleBasedAPIAccessChecker() {
super();
for (RoleType roleType : RoleType.values()) {
annotationRoleBasedApisMap.put(roleType, new HashSet<String>());
annotationRoleBasedApisMap.put(roleType, new HashSet<>());
}
}
@ -67,9 +70,12 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
}
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(role.getId());
List<RolePermissionEntity> allPermissionEntities = allPermissions.stream().map(permission -> (RolePermissionEntity) permission)
.collect(Collectors.toList());
List<String> allowedApis = new ArrayList<>();
for (String api : apiNames) {
if (checkApiPermissionByRole(role, api, allPermissions)) {
if (checkApiPermissionByRole(role, api, allPermissionEntities, false)) {
allowedApis.add(api);
}
}
@ -84,8 +90,8 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
* @param allPermissions list of role permissions for the given role
* @return if the role has the permission for the API
*/
public boolean checkApiPermissionByRole(Role role, String apiName, List<RolePermission> allPermissions) {
for (final RolePermission permission : allPermissions) {
public boolean checkApiPermissionByRole(Role role, String apiName, List<RolePermissionEntity> allPermissions, boolean keyPairOverride) {
for (RolePermissionEntity permission : allPermissions) {
if (!permission.getRule().matches(apiName)) {
continue;
}
@ -94,13 +100,13 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
return false;
}
if (logger.isTraceEnabled()) {
logger.trace(String.format("The API [%s] is allowed for the role %s by the permission [%s].", apiName, role, permission.getRule().toString()));
}
logger.trace("The API [{}] is allowed for the role {} by the permission [{}].", apiName, role, permission.getRule().toString());
return true;
}
return annotationRoleBasedApisMap.get(role.getRoleType()) != null &&
annotationRoleBasedApisMap.get(role.getRoleType()).contains(apiName);
annotationRoleBasedApisMap.get(role.getRoleType()).contains(apiName) &&
!keyPairOverride;
}
protected Account getAccountFromId(long accountId) {
@ -135,49 +141,46 @@ public class DynamicRoleBasedAPIAccessChecker extends AdapterBase implements API
}
@Override
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
Account account = getAccountFromIdUsingCache(user.getAccountId());
if (account == null) {
throw new PermissionDeniedException(String.format("Account for user id [%s] cannot be found", user.getUuid()));
throw new PermissionDeniedException(String.format("Account for user with ID [%s] cannot be found", user.getUuid()));
}
Pair<Role, List<RolePermission>> roleAndPermissions = getRolePermissionsUsingCache(account.getRoleId());
final Role accountRole = roleAndPermissions.first();
if (accountRole == null) {
throw new PermissionDeniedException(String.format("Account role for user id [%s] cannot be found.", user.getUuid()));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
logger.info("Account for user id {} is Root Admin or Domain Admin, all APIs are allowed.", user.getUuid());
return true;
}
List<RolePermission> allPermissions = roleAndPermissions.second();
if (checkApiPermissionByRole(accountRole, commandName, allPermissions)) {
return true;
}
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account for user id [%s].", commandName, user.getUuid()));
return checkAccess(account, commandName, apiKeyPairPermissions);
}
public boolean checkAccess(Account account, String commandName) {
@Override
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission ... apiKeyPairPermissions) {
Pair<Role, List<RolePermission>> roleAndPermissions = getRolePermissionsUsingCache(account.getRoleId());
final Role accountRole = roleAndPermissions.first();
if (accountRole == null) {
throw new PermissionDeniedException(String.format("The account [%s] has role null or unknown.", account));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId()) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Account [%s] is Root Admin or Domain Admin, all APIs are allowed.", account));
}
if (accountRole.getRoleType() == RoleType.Admin && accountRole.getId() == RoleType.Admin.getId() && apiKeyPairPermissions.length == 0) {
logger.info("Account [{}] is Root Admin and there aren't any API key pair permissions involved, thus, all APIs are allowed.", account);
return true;
}
List<RolePermission> allPermissions = roleService.findAllPermissionsBy(accountRole.getId());
if (checkApiPermissionByRole(accountRole, commandName, allPermissions)) {
boolean considerKeyPairPermissions = apiKeyPairPermissions.length > 0;
List<RolePermissionEntity> allRules = considerKeyPairPermissions ? Arrays.asList(apiKeyPairPermissions) : new ArrayList<>(roleAndPermissions.second());
if (checkApiPermissionByRole(accountRole, commandName, allRules, considerKeyPairPermissions)) {
return true;
}
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account %s.", commandName, account));
throw new UnavailableCommandException(String.format("The API [%s] does not exist or is not available for the account %s.", commandName, account.getAccountName()));
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return annotationRoleBasedApisMap.get(roleType)
.stream()
.map(implicitApi -> new RolePermissionBaseVO(implicitApi, Permission.ALLOW))
.collect(Collectors.toList());
}
/**

View File

@ -22,6 +22,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.cloud.exception.UnavailableCommandException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@ -195,4 +197,68 @@ public class DynamicRoleBasedAPIAccessCheckerTest extends TestCase {
List<String> apisReceived = apiAccessCheckerSpy.getApisAllowedToUser(getTestRole(), getTestUser(), apiNames);
Assert.assertEquals(0, apisReceived.size());
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidApiKeyPairPermission() {
final String api = "someDeniedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null);
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, 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));
}
@Test
public void checkAccessTestValidApiKeyPairPermission() {
final String api = "someAllowedApi";
final ApiKeyPairPermission permission = new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null);
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permission));
}
@Test
public void checkAccessTestValidMultipleApiKeyPermissions() {
final String api = "someAllowedApi";
final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
new ApiKeyPairPermissionVO(1L, "someDeniedApi", Permission.DENY, null),
new ApiKeyPairPermissionVO(1L, api, Permission.ALLOW, null)
};
assertTrue(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidMultipleApiKeyPermissions() {
final String api = "someDeniedApi";
final ApiKeyPairPermission[] permissions = new ApiKeyPairPermission[]{
new ApiKeyPairPermissionVO(1L, "someAllowedApi", Permission.ALLOW, null),
new ApiKeyPairPermissionVO(1L, api, Permission.DENY, null)
};
assertFalse(apiAccessCheckerSpy.checkAccess(getTestUser(), api, permissions));
}
@Test
public void checkAccessTestValidApiKeyPairPermissionWithNullOverride() {
final String api = "someAllowedApi";
final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
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));
Mockito.verify(roleServiceMock).findAllPermissionsBy(Mockito.anyLong());
}
@Test(expected = UnavailableCommandException.class)
public void checkAccessTestInvalidApiKeyPairPermissionWithNullOverride() {
final String api = "someDeniedApi";
final ApiKeyPairPermission[] emptyPermissionArray = List.of().toArray(new ApiKeyPairPermission[0]);
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));
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.ApiKeyPairPermission;
import org.apache.cloudstack.context.CallContext;
import com.cloud.exception.PermissionDeniedException;
@ -105,7 +106,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -150,7 +151,7 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
}
@Override
public boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
return true;
}
@ -182,6 +183,11 @@ public class ProjectRoleBasedApiAccessChecker extends AdapterBase implements AP
return true;
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return List.of();
}
@Override
public boolean start() {
return super.start();

View File

@ -21,11 +21,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.UnavailableCommandException;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.APICommand;
@ -90,7 +92,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
}
@Override
public boolean checkAccess(User user, String commandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
@ -104,7 +106,7 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
}
@Override
public boolean checkAccess(Account account, String commandName) {
public boolean checkAccess(Account account, String commandName, ApiKeyPairPermission... apiKeyPairPermissions) {
if (!isEnabled()) {
return true;
}
@ -163,6 +165,14 @@ public class StaticRoleBasedAPIAccessChecker extends AdapterBase implements APIA
return super.start();
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return annotationRoleBasedApisMap.get(roleType)
.stream()
.map(implicitApi -> new RolePermissionBaseVO(implicitApi, RolePermissionEntity.Permission.ALLOW))
.collect(Collectors.toList());
}
private void processMapping(Map<String, String> configMap) {
for (Map.Entry<String, String> entry : configMap.entrySet()) {
String apiName = entry.getKey();

View File

@ -39,6 +39,12 @@
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-api-limit-account-based</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -52,7 +52,7 @@ public class ListApisCmd extends BaseCmd {
public void execute() throws ServerApiException {
if (_apiDiscoveryService != null) {
User user = CallContext.current().getCallingUser();
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>)_apiDiscoveryService.listApis(user, name);
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>)_apiDiscoveryService.listApis(user, name, this);
if (response == null) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Api Discovery plugin was unable to find an api by that name or process any apis");
}

View File

@ -18,6 +18,7 @@ package org.apache.cloudstack.discovery;
import com.cloud.user.Account;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.command.user.discovery.ListApisCmd;
import org.apache.cloudstack.api.response.ListResponse;
import com.cloud.user.User;
@ -28,5 +29,5 @@ import java.util.List;
public interface ApiDiscoveryService extends PluggableService {
List<String> listApiNames(Account account);
ListResponse<? extends BaseResponse> listApis(User user, String apiName);
ListResponse<? extends BaseResponse> listApis(User user, String apiName, ListApisCmd cmd);
}

View File

@ -26,6 +26,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.inject.Inject;
@ -33,6 +34,8 @@ import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.BaseAsyncCreateCmd;
@ -44,6 +47,7 @@ import org.apache.cloudstack.api.response.ApiDiscoveryResponse;
import org.apache.cloudstack.api.response.ApiParameterResponse;
import org.apache.cloudstack.api.response.ApiResponseResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.ratelimit.ApiRateLimitService;
import org.apache.cloudstack.resourcedetail.UserDetailVO;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.apache.commons.collections.CollectionUtils;
@ -78,6 +82,9 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
@Inject
RoleService roleService;
@Inject
ApiKeyPairService apiKeyPairService;
protected ApiDiscoveryServiceImpl() {
super();
}
@ -257,16 +264,24 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
}
@Override
public ListResponse<? extends BaseResponse> listApis(User user, String name) {
public ListResponse<? extends BaseResponse> listApis(User user, String name, ListApisCmd cmd) {
ListResponse<ApiDiscoveryResponse> response = new ListResponse<>();
List<ApiDiscoveryResponse> responseList = new ArrayList<>();
List<String> apisAllowed = new ArrayList<>(s_apiNameDiscoveryResponseMap.keySet());
String apikey = accountService.getAccessingApiKey(cmd);
if (user == null)
return null;
Account account = accountService.getAccount(user.getAccountId());
if (name != null) {
Account account = accountService.getAccount(user.getAccountId());
if (account == null) {
throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user));
}
Role role = roleService.findRole(account.getRoleId());
if (apikey != null) {
responseList = listApisForKeyPair(apikey, name, user, role, apisAllowed);
} else if (name != null) {
if (!s_apiNameDiscoveryResponseMap.containsKey(name))
return null;
@ -281,11 +296,6 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
responseList.add(getApiDiscoveryResponseWithAccessibleParams(name, account));
} else {
if (account == null) {
throw new PermissionDeniedException(String.format("The account with id [%s] for user [%s] is null.", user.getAccountId(), user));
}
final Role role = roleService.findRole(account.getRoleId());
if (role == null || role.getId() < 1L) {
throw new PermissionDeniedException(String.format("The account [%s] has role null or unknown.",
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(account, "accountName", "uuid")));
@ -343,6 +353,44 @@ public class ApiDiscoveryServiceImpl extends ComponentLifecycleBase implements A
return cmdList;
}
protected List<ApiDiscoveryResponse> listApisForKeyPair(String apiKey, String apiName, User user, Role role, List<String> apisAllowed) {
List<RolePermissionEntity> keyPairPermissions = accountService.getAllKeypairPermissions(apiKey);
List<String> filteredApis = new ArrayList<>();
if (apiName != null && isApiAllowedForKey(keyPairPermissions, apiName)) {
filteredApis = List.of(apiName);
} else {
for (String api : apisAllowed) {
if (isApiAllowedForKey(keyPairPermissions, api)) {
filteredApis.add(api);
}
}
}
checkRateLimit(user, role, filteredApis);
return filteredApis.stream().map(api -> s_apiNameDiscoveryResponseMap.get(api)).collect(Collectors.toList());
}
protected boolean isApiAllowedForKey(List<RolePermissionEntity> rolePermissionEntities, String apiName) {
for (RolePermissionEntity rolePermissionEntity : rolePermissionEntities) {
if (!rolePermissionEntity.getRule().matches(apiName)) {
continue;
}
return rolePermissionEntity.getPermission().equals(RolePermissionEntity.Permission.ALLOW);
}
return false;
}
private void checkRateLimit(User user, Role role, List<String> apiNames) {
for (APIChecker apiChecker : _apiAccessCheckers) {
if (!(apiChecker instanceof ApiRateLimitService)) {
continue;
}
apiChecker.getApisAllowedToUser(role, user, apiNames);
return;
}
}
public List<APIChecker> getApiAccessCheckers() {
return _apiAccessCheckers;
}

View File

@ -99,7 +99,7 @@ public class ApiDiscoveryTest {
@Test (expected = PermissionDeniedException.class)
public void listApisTestThrowPermissionDeniedExceptionOnAccountNull() throws PermissionDeniedException {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(null);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test (expected = PermissionDeniedException.class)
@ -107,7 +107,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(null);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test (expected = PermissionDeniedException.class)
@ -117,7 +117,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(unknownRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
}
@Test
@ -128,7 +128,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(adminAccountVO);
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(adminRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(0)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -140,7 +140,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(1)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -153,7 +153,7 @@ public class ApiDiscoveryTest {
Mockito.when(mockUserAccount.getDetails()).thenReturn(userDetails);
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
discoveryServiceSpy.listApis(getTestUser(), null);
discoveryServiceSpy.listApis(getTestUser(), null, null);
Mockito.verify(apiCheckerMock, Mockito.times(1)).getApisAllowedToUser(any(Role.class), any(User.class), anyList());
}
@ -166,7 +166,7 @@ public class ApiDiscoveryTest {
Mockito.when(accountServiceMock.getAccount(Mockito.anyLong())).thenReturn(getNormalAccount());
Mockito.when(roleServiceMock.findRole(Mockito.anyLong())).thenReturn(userRoleVO);
Mockito.when(apiNameDiscoveryResponseMapMock.get(Mockito.anyString())).thenReturn(Mockito.mock(ApiDiscoveryResponse.class));
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>) discoveryServiceSpy.listApis(getTestUser(), null);
ListResponse<ApiDiscoveryResponse> response = (ListResponse<ApiDiscoveryResponse>) discoveryServiceSpy.listApis(getTestUser(), null, null);
Assert.assertEquals(4, response.getResponses().size());
}
}

View File

@ -27,6 +27,9 @@ import net.sf.ehcache.Cache;
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.ApiKeyPairPermission;
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
import org.springframework.stereotype.Component;
@ -161,17 +164,17 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker,
}
@Override
public boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException {
public boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (!isEnabled()) {
return true;
}
Account account = _accountService.getAccount(user.getAccountId());
return checkAccess(account, apiCommandName);
return checkAccess(account, apiCommandName, apiKeyPairPermissions);
}
@Override
public boolean checkAccess(Account account, String commandName) {
public boolean checkAccess(Account account, String commandName, 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.",
@ -207,6 +210,11 @@ public class ApiRateLimitServiceImpl extends AdapterBase implements APIChecker,
return true;
}
@Override
public List<RolePermissionEntity> getImplicitRolePermissions(RoleType roleType) {
return List.of();
}
@Override
public boolean isEnabled() {
if (!enabled) {

View File

@ -252,7 +252,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder {
}
public boolean isUserAllowedToSeeActivationRules(User user) {
List<ApiDiscoveryResponse> apiList = (List<ApiDiscoveryResponse>) apiDiscoveryService.listApis(user, null).getResponses();
List<ApiDiscoveryResponse> apiList = (List<ApiDiscoveryResponse>) apiDiscoveryService.listApis(user, null, null).getResponses();
return apiList.stream().anyMatch(response -> StringUtils.equalsAny(response.getName(), "quotaTariffCreate", "quotaTariffUpdate"));
}

View File

@ -652,7 +652,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}
@ -668,7 +668,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertTrue(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}
@ -684,7 +684,7 @@ public class QuotaResponseBuilderImplTest extends TestCase {
ListResponse<ApiDiscoveryResponse> responseList = new ListResponse<>();
responseList.setResponses(cmdList);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null);
Mockito.doReturn(responseList).when(discoveryServiceMock).listApis(userMock, null, null);
assertFalse(quotaResponseBuilderSpy.isUserAllowedToSeeActivationRules(userMock));
}

View File

@ -263,9 +263,8 @@ public class BaremetalVlanManagerImpl extends ManagerBase implements BaremetalVl
user.setSource(User.Source.UNKNOWN);
user = userDao.persist(user);
String[] keys = acntMgr.createApiKeyAndSecretKey(user.getId());
user.setApiKey(keys[0]);
user.setSecretKey(keys[1]);
acntMgr.createApiKeyAndSecretKey(user.getId());
userDao.update(user.getId(), user);
return true;
}

View File

@ -49,6 +49,7 @@ import javax.naming.ConfigurationException;
import com.cloud.configuration.Resource;
import com.cloud.user.ResourceLimitService;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
@ -1890,12 +1891,12 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
KUBEADMIN_ACCOUNT_NAME, "kubeadmin", null, UUID.randomUUID().toString(), User.Source.UNKNOWN));
keys = createUserApiKeyAndSecretKey(kube.getId());
} else {
String apiKey = kubeadmin.getApiKey();
String secretKey = kubeadmin.getSecretKey();
if (StringUtils.isAnyEmpty(apiKey, secretKey)) {
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(kubeadmin.getId());
if (latestKeypair == null) {
keys = createUserApiKeyAndSecretKey(kubeadmin.getId());
} else {
keys = new String[]{apiKey, secretKey};
keys = new String[]{latestKeypair.getApiKey(), latestKeypair.getSecretKey()};
}
}
return keys;

View File

@ -25,21 +25,30 @@ import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.api.auth.SetupUserTwoFactorAuthenticationCmd;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserTwoFactorAuthenticator;
import org.apache.cloudstack.backup.BackupOffering;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
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.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.context.CallContext;
import com.cloud.api.query.vo.ControlledViewEntity;
@ -119,7 +128,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public String[] createApiKeyAndSecretKey(RegisterUserKeyCmd arg0) {
public ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd arg0) {
// TODO Auto-generated method stub
return null;
}
@ -401,7 +410,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public Pair<User, Account> findUserByApiKey(String arg0) {
public Ternary<User, Account, ApiKeyPair> findUserByApiKey(String arg0) {
// TODO Auto-generated method stub
return null;
}
@ -466,6 +475,10 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
// TODO Auto-generated method stub
}
@Override
public void validateCallingUserHasAccessToDesiredUser(Long userId) {
}
@Override
public Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly) {
// TODO Auto-generated method stub
@ -503,10 +516,23 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
public ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd) {
return null;
}
@Override
public List<ApiKeyPairPermission> listKeyRules(ListUserKeyRulesCmd cmd) {
return null;
}
@Override
public void deleteApiKey(DeleteUserKeysCmd cmd) {
}
@Override
public void deleteApiKey(ApiKeyPair id) {
}
@Override
public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() {
return null;
@ -517,6 +543,31 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
return null;
}
@Override
public ApiKeyPair getLatestUserKeyPair(Long userId) {
return null;
}
@Override
public ApiKeyPair getKeyPairById(Long id) {
return null;
}
@Override
public ApiKeyPair getKeyPairByApiKey(String apiKey) {
return null;
}
@Override
public String getAccessingApiKey(BaseCmd cmd) {
return null;
}
@Override
public List<RolePermissionEntity> getAllKeypairPermissions(String apiKey) {
return List.of();
}
@Override
public void checkAccess(User user, ControlledEntity entity)
throws PermissionDeniedException {
@ -538,7 +589,7 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
}
@Override
public void checkApiAccess(Account account, String command) throws PermissionDeniedException {
public void checkApiAccess(Account account, String command, String apiKey) throws PermissionDeniedException {
}
@Override
@ -549,4 +600,12 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount) {
}
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
}
@Override
public void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
}
}

View File

@ -19,6 +19,7 @@ package org.apache.cloudstack.network.tungsten.service;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.Command;
import com.cloud.api.ApiDBUtils;
import com.cloud.configuration.Config;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.dc.DataCenter;
@ -114,6 +115,7 @@ import net.juniper.tungsten.api.types.TagType;
import net.juniper.tungsten.api.types.VirtualMachine;
import net.juniper.tungsten.api.types.VirtualMachineInterface;
import net.juniper.tungsten.api.types.VirtualNetwork;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -1182,9 +1184,10 @@ public class TungstenServiceImpl extends ManagerBase implements TungstenService
int listenerPort = NetUtils.HTTPS_PORT;
User callerUser = accountMgr.getActiveUser(CallContext.current().getCallingUserId());
String apiKey = callerUser.getApiKey();
String secretKey = callerUser.getSecretKey();
if (apiKey != null && secretKey != null) {
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(callerUser.getId());
if (latestKeypair != null) {
String apiKey = latestKeypair.getApiKey();
String secretKey = latestKeypair.getSecretKey();
String url;
try {
String data = "apiKey=" + URLEncoder.encode(apiKey, StandardCharsets.UTF_8.name()).replace("\\+", "%20") + "&command"

View File

@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.network.tungsten.service;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@ -360,6 +361,10 @@ public class TungstenElementTest {
when(lbStickinessPolicy.getMethodName()).thenReturn("AppCookie");
List<Pair<String, String>> pairList = List.of(new Pair<>("cookieName", "cookieValue"));
ApiKeyPairVO latest = new ApiKeyPairVO();
latest.setApiKey("apikey");
latest.setSecretKey("secretkey");
when(ApiDBUtils.searchForLatestUserKeyPair(Mockito.anyLong())).thenReturn(latest);
when(lbStickinessPolicy.getParams()).thenReturn(pairList);
when(loadBalancingRule1.getId()).thenReturn(1L);
when(loadBalancingRule1.getState()).thenReturn(FirewallRule.State.Add);

View File

@ -55,7 +55,7 @@ public class S3ImageStoreDriverImpl extends BaseImageStoreDriverImpl {
return new S3TO(imgStore.getId(),
imgStore.getUuid(),
details.get(ApiConstants.S3_ACCESS_KEY),
details.get(ApiConstants.S3_SECRET_KEY),
details.get(ApiConstants.SECRET_KEY),
details.get(ApiConstants.S3_END_POINT),
details.get(ApiConstants.S3_BUCKET_NAME),
details.get(ApiConstants.S3_SIGNER),

View File

@ -321,7 +321,6 @@ import com.cloud.template.TemplateManager;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account;
import com.cloud.user.AccountDetailsDao;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountService;
import com.cloud.user.AccountVO;
import com.cloud.user.ResourceLimitService;
@ -361,6 +360,8 @@ import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -498,6 +499,7 @@ public class ApiDBUtils {
static BackupRepositoryDao s_backupRepositoryDao;
static NicDao s_nicDao;
static ResourceManagerUtil s_resourceManagerUtil;
static ApiKeyPairDao s_apiKeyPairDao;
static SnapshotPolicyDetailsDao s_snapshotPolicyDetailsDao;
static ObjectStoreDao s_objectStoreDao;
@ -764,6 +766,8 @@ public class ApiDBUtils {
@Inject
private ResourceManagerUtil resourceManagerUtil;
@Inject
private ApiKeyPairDao apiKeyPairDao;
@Inject
SnapshotPolicyDetailsDao snapshotPolicyDetailsDao;
@Inject
@ -907,6 +911,7 @@ public class ApiDBUtils {
s_backupRepositoryDao = backupRepositoryDao;
s_resourceIconDao = resourceIconDao;
s_resourceManagerUtil = resourceManagerUtil;
s_apiKeyPairDao = apiKeyPairDao;
s_objectStoreDao = objectStoreDao;
s_bucketDao = bucketDao;
s_virtualMachineManager = virtualMachineManager;
@ -1984,10 +1989,8 @@ public class ApiDBUtils {
}
public static UserResponse newUserResponse(ResponseView view, Long domainId, UserAccountJoinVO usr) {
UserResponse response = s_userAccountJoinDao.newUserResponse(view, usr);
if(!AccountManager.UseSecretKeyInResponse.value()){
response.setSecretKey(null);
}
ApiKeyPairVO lastKeyPair = searchForLatestUserKeyPair(usr.getId());
UserResponse response = s_userAccountJoinDao.newUserResponse(view, usr, lastKeyPair);
// Populate user account role information
if (usr.getAccountRoleId() != null) {
Role role = s_roleService.findRole( usr.getAccountRoleId());
@ -2004,6 +2007,10 @@ public class ApiDBUtils {
return response;
}
public static ApiKeyPairVO searchForLatestUserKeyPair(Long userId) {
return s_apiKeyPairDao.getLastApiKeyCreatedByUser(userId);
}
public static UserAccountJoinVO newUserView(User usr) {
return s_userAccountJoinDao.newUserView(usr);
}

View File

@ -39,8 +39,16 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.domain.dao.DomainDao;
import com.cloud.user.AccountVO;
import com.cloud.user.ApiKeyPairState;
import com.cloud.user.dao.AccountDao;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.acl.RoleVO;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.dao.RoleDao;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.annotation.AnnotationService;
@ -66,6 +74,7 @@ import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse;
import org.apache.cloudstack.api.response.BackupOfferingResponse;
import org.apache.cloudstack.api.response.BackupRepositoryResponse;
import org.apache.cloudstack.api.response.BackupScheduleResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.BgpPeerResponse;
import org.apache.cloudstack.api.response.BucketResponse;
import org.apache.cloudstack.api.response.CapabilityResponse;
@ -113,6 +122,7 @@ import org.apache.cloudstack.api.response.IpRangeResponse;
import org.apache.cloudstack.api.response.Ipv4RouteResponse;
import org.apache.cloudstack.api.response.Ipv6RouteResponse;
import org.apache.cloudstack.api.response.IsolationMethodResponse;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.LBHealthCheckPolicyResponse;
import org.apache.cloudstack.api.response.LBHealthCheckResponse;
import org.apache.cloudstack.api.response.LBStickinessPolicyResponse;
@ -542,6 +552,15 @@ public class ApiResponseHelper implements ResponseGenerator {
return domainPath.toString();
}
@Inject
private RoleDao roleDao;
@Inject
private AccountDao accountDao;
@Inject
private DomainDao domainDao;
@Override
public UserResponse createUserResponse(User user) {
UserAccountJoinVO vUser = ApiDBUtils.newUserView(user);
@ -5707,4 +5726,79 @@ protected Map<String, ResourceIcon> getResourceIconsUsingOsCategory(List<Templat
consoleSessionResponse.setObjectName("consolesession");
return consoleSessionResponse;
}
@Override
public ApiKeyPairResponse createKeyPairResponse(ApiKeyPair keyPair) {
ApiKeyPairResponse apiKeyPairResponse = new ApiKeyPairResponse();
populateApiKeyPairInApiKeyPairResponse(keyPair, apiKeyPairResponse);
populateUserInApiKeyPairResponse(keyPair, apiKeyPairResponse);
AccountVO account = accountDao.findByIdIncludingRemoved(keyPair.getAccountId());
apiKeyPairResponse.setAccountId(account.getUuid());
apiKeyPairResponse.setAccountName(account.getAccountName());
apiKeyPairResponse.setAccountType(account.getType().toString());
populateDomainInApiKeyPairResponse(account.getDomainId(), apiKeyPairResponse);
populateRoleInApiKeyPairResponse(account.getRoleId(), apiKeyPairResponse);
return apiKeyPairResponse;
}
protected void populateRoleInApiKeyPairResponse(Long roleId, ApiKeyPairResponse apiKeyPairResponse) {
RoleVO roleVO = roleDao.findById(roleId);
apiKeyPairResponse.setRoleId(roleVO.getUuid());
apiKeyPairResponse.setRoleName(roleVO.getName());
apiKeyPairResponse.setRoleType(roleVO.getRoleType().name());
}
protected static void populateApiKeyPairInApiKeyPairResponse(ApiKeyPair keyPair, ApiKeyPairResponse apiKeyPairResponse) {
apiKeyPairResponse.setName(keyPair.getName());
apiKeyPairResponse.setApiKey(keyPair.getApiKey());
apiKeyPairResponse.setSecretKey(keyPair.getSecretKey());
apiKeyPairResponse.setDescription(keyPair.getDescription());
apiKeyPairResponse.setId(keyPair.getUuid());
apiKeyPairResponse.setCreated(keyPair.getCreated());
apiKeyPairResponse.setStartDate(keyPair.getStartDate());
apiKeyPairResponse.setEndDate(keyPair.getEndDate());
ApiKeyPairState state = ApiKeyPairState.ENABLED;
if (keyPair.getRemoved() != null) {
state = ApiKeyPairState.REMOVED;
} else if (keyPair.hasEndDatePassed()) {
state = ApiKeyPairState.EXPIRED;
}
apiKeyPairResponse.setState(state);
}
protected void populateUserInApiKeyPairResponse(ApiKeyPair keyPair, ApiKeyPairResponse apiKeyPairResponse) {
User user = ApiDBUtils.findUserById(keyPair.getUserId());
apiKeyPairResponse.setUserId(user.getUuid());
apiKeyPairResponse.setUsername(user.getUsername());
}
protected void populateDomainInApiKeyPairResponse(Long domainId, ApiKeyPairResponse apiKeyPairResponse) {
DomainVO domainVO = domainDao.findById(domainId);
apiKeyPairResponse.setDomainId(domainVO.getUuid());
apiKeyPairResponse.setDomainName(domainVO.getName());
StringBuilder domainPath = new StringBuilder("ROOT");
(domainPath.append(domainVO.getPath())).deleteCharAt(domainPath.length() - 1);
apiKeyPairResponse.setDomainPath(domainPath.toString());
}
@Override
public ListResponse<BaseRolePermissionResponse> createKeypairPermissionsResponse(final List<ApiKeyPairPermission> permissions) {
final ListResponse<BaseRolePermissionResponse> response = new ListResponse<>();
final List<BaseRolePermissionResponse> permissionResponses = new ArrayList<>();
for (final ApiKeyPairPermission permission : permissions) {
BaseRolePermissionResponse permissionResponse = new BaseRolePermissionResponse();
permissionResponse.setRule(permission.getRule());
permissionResponse.setRulePermission(permission.getPermission());
permissionResponse.setDescription(permission.getDescription());
permissionResponse.setObjectName("keypermission");
permissionResponses.add(permissionResponse);
}
response.setResponses(permissionResponses);
return response;
}
}

View File

@ -60,6 +60,7 @@ import javax.servlet.http.HttpSession;
import com.cloud.cluster.ManagementServerHostVO;
import com.cloud.cluster.dao.ManagementServerHostDao;
import com.cloud.utils.Ternary;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountManagerImpl;
@ -68,6 +69,9 @@ import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.user.UserVO;
import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
@ -237,6 +241,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
private UUIDManager uuidMgr;
@Inject
private UserPasswordResetManager userPasswordResetManager;
@Inject
private ApiKeyPairManagerImpl keyPairManager;
private List<PluggableService> pluggableServices;
@ -1081,14 +1087,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
txn.close();
User user;
// verify there is a user with this api key
final Pair<User, Account> userAcctPair = accountMgr.findUserByApiKey(apiKey);
if (userAcctPair == null) {
final Ternary<User, Account, ApiKeyPair> keyPairTernary = accountMgr.findUserByApiKey(apiKey);
if (keyPairTernary == null) {
logger.debug("apiKey does not map to a valid user -- ignoring request, apiKey: {}", apiKey);
return false;
}
user = userAcctPair.first();
final Account account = userAcctPair.second();
user = keyPairTernary.first();
Account account = keyPairTernary.second();
ApiKeyPair keyPair = keyPairTernary.third();
if (user.getState() != Account.State.ENABLED || !account.getState().equals(Account.State.ENABLED)) {
logger.info("disabled or locked user accessing the api, user = {} (state: {}); " +
@ -1104,10 +1112,16 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return false;
}
// verify secret key exists
secretKey = user.getSecretKey();
if (keyPair.getRemoved() != null) {
logger.info(String.format("Invalid request, as used API keypair [%s] has been removed.", keyPair.getUuid()));
return false;
}
keyPair.validateDate();
secretKey = keyPair.getSecretKey();
if (secretKey == null) {
logger.info("User does not have a secret key associated with the account -- ignoring request, username: {}", user);
logger.info(String.format("User does not have a secret key associated with the API key -- ignoring request, username: [%s].", user.getUsername()));
return false;
}
@ -1125,21 +1139,28 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (!equalSig) {
signature = signature.replaceAll(SANITIZATION_REGEX, "_");
logger.info("User signature [{}] is not equaled to computed signature [{}].", signature, computedSignature);
} else {
CallContext.register(user, account);
return false;
}
CallContext.register(user, account);
List<ApiKeyPairPermission> keyPairPermissions = keyPairManager.findAllPermissionsByKeyPairId(keyPair.getId(), account.getRoleId());
if (commandAvailable(remoteAddress, commandName, user, keyPairPermissions.toArray(new ApiKeyPairPermission[0]))) {
logger.info("API accessed through API Key Pair. API Key: [{}].", keyPair.getApiKey());
return true;
}
return equalSig;
} catch (final ServerApiException ex) {
throw ex;
} catch (PermissionDeniedException ex) {
logger.error("Permission denied for keypair, reason: {}.", ex.getMessage());
} catch (final Exception ex) {
logger.error("unable to verify request signature");
logger.error("Unable to verify request signature.", ex);
}
return false;
}
private boolean commandAvailable(final InetAddress remoteAddress, final String commandName, final User user) {
private boolean commandAvailable(final InetAddress remoteAddress, final String commandName, final User user, ApiKeyPairPermission... rolePermissions) {
try {
checkCommandAvailable(user, commandName, remoteAddress);
checkCommandAvailable(user, commandName, remoteAddress, rolePermissions);
} catch (final RequestLimitException ex) {
logger.debug(ex.getMessage());
throw new ServerApiException(ApiErrorCode.API_LIMIT_EXCEED, ex.getMessage());
@ -1432,7 +1453,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
return domainIdArr[0];
}
private void checkCommandAvailable(final User user, final String commandName, final InetAddress remoteAddress) throws PermissionDeniedException {
private void checkCommandAvailable(final User user, final String commandName, final InetAddress remoteAddress, ApiKeyPairPermission ... apiKeyPairPermissions) throws PermissionDeniedException {
if (user == null) {
throw new PermissionDeniedException("User is null for role based API access check for command" + commandName);
}
@ -1446,12 +1467,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
if (!NetUtils.isIpInCidrList(remoteAddress, accessAllowedCidrs.split(","))) {
logger.warn("Request by account '{}' was denied since {} does not match {}", account.toString(), remoteAddress, accessAllowedCidrs);
throw new OriginDeniedException("Calls from disallowed origin", account, remoteAddress);
}
}
}
for (final APIChecker apiChecker : apiAccessCheckers) {
apiChecker.checkAccess(user, commandName);
apiChecker.checkAccess(user, commandName, apiKeyPairPermissions);
}
}

View File

@ -343,7 +343,7 @@ public class ApiServlet extends HttpServlet {
return;
}
} else {
LOGGER.trace("no command available");
LOGGER.trace("No command available.");
}
auditTrailSb.append(cleanQueryString);
final boolean isNew = ((session == null) ? true : session.isNew());
@ -353,7 +353,7 @@ public class ApiServlet extends HttpServlet {
// if a API key exists
if (isNew && LOGGER.isTraceEnabled()) {
LOGGER.trace(String.format("new session: %s", session));
LOGGER.trace(String.format("New session: %s.", session));
}
if (!isNew && (command.equalsIgnoreCase(ValidateUserTwoFactorAuthenticationCodeCmd.APINAME) || (!skip2FAcheckForAPIs(command) && !skip2FAcheckForUser(session)))) {

View File

@ -37,6 +37,13 @@ import java.util.stream.Stream;
import javax.inject.Inject;
import com.cloud.network.PublicIpQuarantine;
import com.cloud.network.dao.PublicIpQuarantineDao;
import com.cloud.network.vo.PublicIpQuarantineVO;
import com.cloud.user.UserVO;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleVO;
import org.apache.cloudstack.acl.dao.RoleDao;
import com.cloud.dc.Pod;
import com.cloud.dc.dao.DataCenterDao;
import com.cloud.dc.dao.HostPodDao;
@ -178,6 +185,7 @@ import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.utils.baremetal.BaremetalUtils;
import org.apache.cloudstack.vm.lease.VMLeaseManager;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
@ -262,7 +270,6 @@ import com.cloud.host.dao.HostDao;
import com.cloud.host.dao.HostTagsDao;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.PublicIpQuarantine;
import com.cloud.network.RouterHealthCheckResult;
import com.cloud.network.VNF;
import com.cloud.network.VpcVirtualNetworkApplianceService;
@ -272,13 +279,11 @@ import com.cloud.network.dao.IPAddressDao;
import com.cloud.network.dao.IPAddressVO;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PublicIpQuarantineDao;
import com.cloud.network.dao.RouterHealthCheckResultDao;
import com.cloud.network.dao.RouterHealthCheckResultVO;
import com.cloud.network.router.VirtualNetworkApplianceManager;
import com.cloud.network.security.SecurityGroupVMMapVO;
import com.cloud.network.security.dao.SecurityGroupVMMapDao;
import com.cloud.network.vo.PublicIpQuarantineVO;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.ServiceOffering;
import com.cloud.org.Grouping;
@ -377,6 +382,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Inject
AccountManager accountMgr;
@Inject
RoleService roleService;
@Inject
ProjectManager _projectMgr;
@ -646,6 +654,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
@Inject
ExtensionHelper extensionHelper;
@Inject
RoleDao roleDao;
/*
* (non-Javadoc)
*
@ -825,6 +836,37 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
return _userAccountJoinDao.searchAndCount(sc, searchFilter);
}
@Override
public List<Long> searchForAccessibleUsers() {
List<Long> permittedAccounts = new ArrayList<>();
Account callingAccount = CallContext.current().getCallingAccount();
Filter searchFilter = new Filter(UserAccountJoinVO.class, "id", true);
List<RoleVO> allowedRoles = roleDao.listAll();
roleService.removeRolesIfNeeded(allowedRoles);
List<Long> allowedRolesId = allowedRoles.stream().map(RoleVO::getId).collect(Collectors.toList());
Pair<List<UserAccountJoinVO>, Integer> usersPair = getUserListInternal(callingAccount, permittedAccounts,
true, null, null, null, null, null, null, null, callingAccount.getDomainId(), true, searchFilter, null);
return usersPair.first().stream().filter(userAccount -> {
if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(userAccount.getUsername()) && !accountMgr.isRootAdmin(callingAccount.getId())) {
return false;
}
AccountVO accountVO = _accountDao.findByIdIncludingRemoved(userAccount.getAccountId());
UserVO userVO = userDao.findByIdIncludingRemoved(userAccount.getId());
if (ObjectUtils.anyNull(accountVO, userVO)) {
return false;
}
try {
accountMgr.checkCallerRoleTypeAllowedForUserOrAccountOperations(accountVO, userVO);
} catch (PermissionDeniedException exception) {
return false;
}
return allowedRolesId.contains(userAccount.getAccountRoleId());
}).map(UserAccountJoinVO::getId).collect(Collectors.toList());
}
@Override
public ListResponse<EventResponse> searchForEvents(ListEventsCmd cmd) {
Pair<List<EventJoinVO>, Integer> result = searchForEventsInternal(cmd);

View File

@ -19,6 +19,7 @@ package com.cloud.api.query.dao;
import java.util.List;
import org.apache.cloudstack.api.ResponseObject;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.api.response.UserResponse;
import com.cloud.api.query.vo.UserAccountJoinVO;
@ -28,7 +29,7 @@ import com.cloud.utils.db.GenericDao;
public interface UserAccountJoinDao extends GenericDao<UserAccountJoinVO, Long> {
UserResponse newUserResponse(ResponseObject.ResponseView responseView, UserAccountJoinVO usr);
UserResponse newUserResponse(ResponseObject.ResponseView responseView, UserAccountJoinVO usr, ApiKeyPairVO lastKeyPair);
UserAccountJoinVO newUserView(User usr);

View File

@ -19,8 +19,10 @@ package com.cloud.api.query.dao;
import java.util.List;
import com.cloud.user.AccountManager;
import com.cloud.user.AccountManagerImpl;
import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.api.response.UserResponse;
@ -53,7 +55,7 @@ public class UserAccountJoinDaoImpl extends GenericDaoBase<UserAccountJoinVO, Lo
}
@Override
public UserResponse newUserResponse(ResponseView view, UserAccountJoinVO usr) {
public UserResponse newUserResponse(ResponseView view, UserAccountJoinVO usr, ApiKeyPairVO lastKeyPair) {
UserResponse userResponse = new UserResponse();
userResponse.setAccountId(usr.getAccountUuid());
userResponse.setAccountName(usr.getAccountName());
@ -69,10 +71,14 @@ public class UserAccountJoinDaoImpl extends GenericDaoBase<UserAccountJoinVO, Lo
userResponse.setState(usr.getState().toString());
userResponse.setTimezone(usr.getTimezone());
userResponse.setUsername(usr.getUsername());
userResponse.setApiKey(usr.getApiKey());
userResponse.setSecretKey(usr.getSecretKey());
userResponse.setIsDefault(usr.isDefault());
userResponse.set2FAenabled(usr.isUser2faEnabled());
if (lastKeyPair != null) {
userResponse.setApiKey(lastKeyPair.getApiKey());
if (AccountManager.UseSecretKeyInResponse.value()) {
userResponse.setSecretKey(lastKeyPair.getSecretKey());
}
}
long domainId = usr.getDomainId();
boolean is2FAmandated = Boolean.TRUE.equals(AccountManagerImpl.enableUserTwoFactorAuthentication.valueIn(domainId)) && Boolean.TRUE.equals(AccountManagerImpl.mandateUserTwoFactorAuthentication.valueIn(domainId));
userResponse.set2FAmandated(is2FAmandated);

View File

@ -19,7 +19,6 @@ package com.cloud.api.query.vo;
import com.cloud.user.Account;
import com.cloud.user.User;
import com.cloud.user.UserAccount;
import com.cloud.utils.db.Encrypt;
import com.cloud.utils.db.GenericDao;
import org.apache.cloudstack.api.Identity;
import org.apache.cloudstack.api.InternalIdentity;
@ -61,13 +60,6 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I
@Column(name = "state")
private String state;
@Column(name = "api_key")
private String apiKey = null;
@Encrypt
@Column(name = "secret_key")
private String secretKey = null;
@Column(name = GenericDao.CREATED_COLUMN)
private Date created;
@ -209,14 +201,6 @@ public class UserAccountJoinVO extends BaseViewVO implements InternalIdentity, I
return state;
}
public String getApiKey() {
return apiKey;
}
public String getSecretKey() {
return secretKey;
}
public Date getCreated() {
return created;
}

View File

@ -38,6 +38,8 @@ import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import com.cloud.network.NetworkModel;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.affinity.AffinityGroupVO;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
@ -112,7 +114,6 @@ import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.IpAddresses;
import com.cloud.network.NetworkModel;
import com.cloud.network.as.AutoScaleCounter.AutoScaleCounterParam;
import com.cloud.network.as.dao.AutoScalePolicyConditionMapDao;
import com.cloud.network.as.dao.AutoScalePolicyDao;
@ -518,15 +519,10 @@ public class AutoScaleManagerImpl extends ManagerBase implements AutoScaleManage
throw new InvalidParameterValueException("AutoScale User id does not belong to the same account");
}
String apiKey = user.getApiKey();
String secretKey = user.getSecretKey();
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(user.getId());
if (apiKey == null) {
throw new InvalidParameterValueException("apiKey for user: " + user.getUsername() + " is empty. Please generate it");
}
if (secretKey == null) {
throw new InvalidParameterValueException("secretKey for user: " + user.getUsername() + " is empty. Please generate it");
if (latestKeypair == null) {
throw new InvalidParameterValueException(String.format("No API keypair for user [%s]. Please generate it.", user.getUsername()));
}
ApiServiceConfiguration.validateEndpointUrl();

View File

@ -16,6 +16,7 @@
// under the License.
package com.cloud.network.lb;
import com.cloud.api.ApiDBUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -31,6 +32,7 @@ import javax.inject.Inject;
import com.cloud.offerings.NetworkOfferingServiceMapVO;
import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
@ -347,15 +349,15 @@ public class LoadBalancingRulesManagerImpl<Type> extends ManagerBase implements
if (user == null) {
throw new InvalidParameterValueException("Unable to find user by id " + autoscaleUserId);
}
apiKey = user.getApiKey();
secretKey = user.getSecretKey();
if (apiKey == null) {
throw new InvalidParameterValueException("apiKey for user: " + user.getUsername() + " is empty. Please generate it");
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(user.getId());
if (latestKeypair == null) {
throw new InvalidParameterValueException(String.format("No API keypair for user [%s]. Please generate it.", user.getUsername()));
}
if (secretKey == null) {
throw new InvalidParameterValueException("secretKey for user: " + user.getUsername() + " is empty. Please generate it");
}
apiKey = latestKeypair.getApiKey();
secretKey = latestKeypair.getSecretKey();
ApiServiceConfiguration.validateEndpointUrl();
}

View File

@ -17,6 +17,7 @@
package com.cloud.network.router;
import com.cloud.api.ApiDBUtils;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import static com.cloud.vm.VirtualMachineManager.SystemVmEnableUserData;
@ -51,6 +52,7 @@ import javax.naming.ConfigurationException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.alert.AlertService;
import org.apache.cloudstack.alert.AlertService.AlertType;
import org.apache.cloudstack.api.ApiCommandResourceType;
@ -2083,8 +2085,14 @@ Configurable, StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualM
if (user == null) {
logger.warn("global setting[baremetal.provision.done.notification] is enabled but user baremetal-system-account is not found. Baremetal provision done notification will not be enabled");
} else {
buf.append(String.format(" baremetalnotificationsecuritykey=%s", user.getSecretKey()));
buf.append(String.format(" baremetalnotificationapikey=%s", user.getApiKey()));
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(user.getId());
if (latestKeypair == null) {
throw new InvalidParameterValueException(String.format("No API keypair for user [%s]. Please generate it.", user.getUsername()));
}
buf.append(String.format(" baremetalnotificationsecuritykey=%s", latestKeypair.getSecretKey()));
buf.append(String.format(" baremetalnotificationapikey=%s", latestKeypair.getApiKey()));
buf.append(" host=").append(ApiServiceConfiguration.ManagementServerAddresses.value());
buf.append(" port=").append(_configDao.getValue(Config.BaremetalProvisionDoneNotificationPort.key()));
}

View File

@ -43,6 +43,7 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import com.cloud.network.vpc.VpcVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.SecurityChecker;
@ -275,15 +276,18 @@ import org.apache.cloudstack.api.command.admin.usage.ListUsageTypesCmd;
import org.apache.cloudstack.api.command.admin.usage.RemoveRawUsageRecordsCmd;
import org.apache.cloudstack.api.command.admin.usage.UpdateTrafficTypeCmd;
import org.apache.cloudstack.api.command.admin.user.CreateUserCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.DisableUserCmd;
import org.apache.cloudstack.api.command.admin.user.EnableUserCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
import org.apache.cloudstack.api.command.admin.user.LockUserCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
import org.apache.cloudstack.api.command.admin.vlan.DedicatePublicIpRangeCmd;
@ -3947,7 +3951,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ListUsersCmd.class);
cmdList.add(LockUserCmd.class);
cmdList.add(MoveUserCmd.class);
cmdList.add(RegisterUserKeyCmd.class);
cmdList.add(RegisterUserKeysCmd.class);
cmdList.add(UpdateUserCmd.class);
cmdList.add(CreateVlanIpRangeCmd.class);
cmdList.add(UpdateVlanIpRangeCmd.class);
@ -4375,6 +4379,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(CreateConsoleEndpointCmd.class);
cmdList.add(ListConsoleSessionsCmd.class);
cmdList.add(DeleteUserKeysCmd.class);
cmdList.add(ListUserKeysCmd.class);
cmdList.add(ListUserKeyRulesCmd.class);
//user data APIs
cmdList.add(RegisterUserDataCmd.class);
cmdList.add(DeleteUserDataCmd.class);
@ -4777,7 +4785,13 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
try {
// get the user obj to get their secret key
user = _accountMgr.getActiveUser(userId);
final String secretKey = user.getSecretKey();
ApiKeyPairVO latestKeypair = ApiDBUtils.searchForLatestUserKeyPair(user.getId());
if (latestKeypair == null) {
throw new InvalidParameterValueException(String.format("No API keypair for user [%s]. Please generate it.", user.getUsername()));
}
final String secretKey = latestKeypair.getSecretKey();
signature = signRequest(cloudIdentifier, secretKey);
} catch (final Exception e) {
logger.warn("Exception whilst creating a signature:" + e);

View File

@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.framework.security.keys.KeysManager;
import org.apache.commons.codec.binary.Base64;
import org.apache.logging.log4j.Logger;
@ -543,24 +544,23 @@ public class ConsoleProxyServlet extends HttpServlet {
txn.close();
User user = null;
// verify there is a user with this api key
Pair<User, Account> userAcctPair = _accountMgr.findUserByApiKey(apiKey);
if (userAcctPair == null) {
Ternary<User, Account, ApiKeyPair> keyPairTernary = _accountMgr.findUserByApiKey(apiKey);
if (keyPairTernary == null) {
LOGGER.debug("apiKey does not map to a valid user -- ignoring request, apiKey: " + apiKey);
return false;
}
user = userAcctPair.first();
Account account = userAcctPair.second();
user = keyPairTernary.first();
Account account = keyPairTernary.second();
ApiKeyPair keyPair = keyPairTernary.third();
if (!user.getState().equals(Account.State.ENABLED) || !account.getState().equals(Account.State.ENABLED)) {
LOGGER.debug("disabled or locked user accessing the api, user: {}; state: {}; accountState: {}", user, user.getState(), account.getState());
return false;
}
// verify secret key exists
secretKey = user.getSecretKey();
if (secretKey == null) {
LOGGER.debug("User does not have a secret key associated with the account -- ignoring request, user: {}", user);
if (keyPair == null) {
LOGGER.debug("User does not have a keypair associated with the account -- ignoring request, username: {}", user.getUsername());
return false;
}

View File

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
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.MoveUserCmd;
@ -35,7 +36,6 @@ import com.cloud.api.query.vo.ControlledViewEntity;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.projects.Project.ListProjectResourcesCriteria;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
@ -85,7 +85,7 @@ public interface AccountManager extends AccountService, Configurable {
* that was created for a particular user
* @return the user/account pair if one exact match was found, null otherwise
*/
Pair<User, Account> findUserByApiKey(String apiKey);
Ternary<User, Account, ApiKeyPair> findUserByApiKey(String apiKey);
boolean enableAccount(long accountId);
@ -202,9 +202,13 @@ public interface AccountManager extends AccountService, Configurable {
void validateUserPasswordAndUpdateIfNeeded(String newPassword, UserVO user, String currentPassword, boolean skipCurrentPassValidation);
void checkApiAccess(Account caller, String command);
void checkApiAccess(Account caller, String command, String apiKey);
UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user);
void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount);
void verifyCallerPrivilegeForUserOrAccountOperations(User user);
void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user);
}

View File

@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -44,27 +45,48 @@ import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
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 org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.InfrastructureEntity;
import org.apache.cloudstack.acl.QuerySelector;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermission;
import org.apache.cloudstack.acl.RolePermissionEntity;
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.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
import org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDao;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseAsyncCmd;
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.DeleteUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeyCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.ApiKeyPairResponse;
import org.apache.cloudstack.api.response.BaseRolePermissionResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserAuthenticator;
import org.apache.cloudstack.auth.UserAuthenticator.ActionOnFailedAuthentication;
@ -78,6 +100,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.network.RoutedIpv4Manager;
import org.apache.cloudstack.network.dao.NetworkPermissionDao;
import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
@ -88,6 +111,8 @@ import org.apache.cloudstack.webhook.WebhookHelper;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@ -170,17 +195,12 @@ import com.cloud.storage.snapshot.SnapshotManager;
import com.cloud.template.TemplateManager;
import com.cloud.template.VirtualMachineTemplate;
import com.cloud.user.Account.State;
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.cloud.user.dao.UserDataDao;
import com.cloud.utils.ConstantTimeComparator;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.Manager;
import com.cloud.utils.component.ManagerBase;
@ -212,6 +232,8 @@ import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotManager;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.cloudstack.api.BaseCmd;
import org.joda.time.DateTime;
public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager {
@ -228,6 +250,14 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Inject
private InstanceGroupDao _vmGroupDao;
@Inject
private ApiKeyPairDao apiKeyPairDao;
@Inject
private ApiKeyPairService apiKeyPairService;
@Inject
private ApiKeyPairPermissionsDao apiKeyPairPermissionsDao;
@Inject
private ApiKeyPairManagerImpl keyPairManager;
@Inject
private UserAccountDao userAccountDao;
@Inject
private VolumeDao _volumeDao;
@ -294,6 +324,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Inject
private VolumeApiService volumeService;
@Inject
private QueryService queryService;
@Inject
private AffinityGroupDao _affinityGroupDao;
@Inject
private AccountGuestVlanMapDao _accountGuestVlanMapDao;
@ -398,6 +430,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
true,
ConfigKey.Scope.Domain);
private Map<RoleType, Set<String>> annotationRoleBasedApisMap = new HashMap<>();
static ConfigKey<Boolean> userAllowMultipleAccounts = new ConfigKey<>("Advanced",
Boolean.class,
"user.allow.multiple.accounts",
@ -424,6 +458,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
protected AccountManagerImpl() {
super();
for (RoleType roleType : RoleType.values()) {
annotationRoleBasedApisMap.put(roleType, new HashSet<>());
}
}
public List<UserAuthenticator> getUserAuthenticators() {
@ -903,6 +940,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return cleanupAccount(account, callerUserId, caller);
}
protected void removeUserApiKeys(Long userId) {
List<ApiKeyPairVO> apiKeyPairs = apiKeyPairDao.listApiKeysByUserOrApiKeyId(userId, null).first();
apiKeyPairs.forEach(keyPair -> _accountService.deleteApiKey(keyPair));
}
protected void cleanupPluginsResourcesIfNeeded(Account account) {
try {
KubernetesServiceHelper kubernetesServiceHelper =
@ -921,6 +963,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// cleanup the users from the account
List<UserVO> users = _userDao.listByAccount(accountId);
for (UserVO user : users) {
removeUserApiKeys(user.getId());
if (!_userDao.remove(user.getId())) {
logger.error("Unable to delete user: " + user + " as a part of account " + account + " cleanup");
accountCleanupNeeded = true;
@ -1469,16 +1512,25 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
}
private void checkApiAccess(List<APIChecker> apiCheckers, Account caller, String command) {
private void checkApiAccess(List<APIChecker> apiCheckers, Account caller, String command, ApiKeyPairPermission... apiKeyPairPermissions) {
for (final APIChecker apiChecker : apiCheckers) {
apiChecker.checkAccess(caller, command);
apiChecker.checkAccess(caller, command, apiKeyPairPermissions);
}
}
@Override
public void checkApiAccess(Account caller, String command) {
public void checkApiAccess(Account caller, String command, String apiKey) {
List<APIChecker> apiCheckers = getEnabledApiCheckers();
checkApiAccess(apiCheckers, caller, command);
List<ApiKeyPairPermission> keyPairPermissions = new ArrayList<>();
if (apiKey != null) {
Ternary<User, Account, ApiKeyPair> keyPairTernary = findUserByApiKey(apiKey);
if (keyPairTernary != null) {
keyPairPermissions = keyPairManager.findAllPermissionsByKeyPairId(keyPairTernary.third().getId(), caller.getRoleId());
}
}
checkApiAccess(apiCheckers, caller, command, keyPairPermissions.toArray(new ApiKeyPairPermission[0]));
}
@NotNull
@ -1641,8 +1693,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
}
protected void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
logger.debug(String.format("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: %s", user));
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
logger.debug("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: {}", user);
Account userAccount = getAccount(user.getAccountId());
if (!Account.Type.PROJECT.equals(userAccount.getType())) {
@ -1651,7 +1704,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
}
protected void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
@Override
public void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
Account callingAccount = getCurrentCallingAccount();
RoleType callerRoleType = getRoleType(callingAccount);
RoleType userAccountRoleType = getRoleType(userAccount);
@ -1892,7 +1946,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
/**
* Validates user API and Secret keys. If a new pair of keys is provided, we update them in the user POJO.
* Validates user API and Secret keys. If a new pair of keys is provided, we update them in the key pair POJO.
* <ul>
* <li>When updating the keys, it must be provided a pair (API and Secret keys); otherwise, an {@link InvalidParameterValueException} is thrown.
* <li>If a pair of keys is provided, we validate to see if there is an user already using the provided API key. If there is someone else using, we throw an {@link InvalidParameterValueException} because two users cannot have the same API key.
@ -1905,19 +1959,25 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
boolean isApiKeyBlank = StringUtils.isBlank(apiKey);
boolean isSecretKeyBlank = StringUtils.isBlank(secretKey);
if (isApiKeyBlank ^ isSecretKeyBlank) {
throw new InvalidParameterValueException("Please provide a userApiKey/userSecretKey pair");
throw new InvalidParameterValueException("Please provide a valid API and secret key pair.");
}
if (isApiKeyBlank && isSecretKeyBlank) {
return;
}
UserAccount apiKeyOwner = userAccountDao.getUserByApiKey(apiKey);
if (apiKeyOwner != null) {
if (apiKeyOwner.getId() != user.getId()) {
throw new InvalidParameterValueException(String.format("The API key [%s] already exists in the system. Please provide a unique key.", apiKey));
}
ApiKeyPairVO lastUserKeyPair = apiKeyPairDao.getLastApiKeyCreatedByUser(user.getId());
if (lastUserKeyPair == null) {
throw new InvalidParameterValueException(String.format("User [%s] has no active API key pairs to be updated.", user.getUsername()));
}
user.setApiKey(apiKey);
user.setSecretKey(secretKey);
Ternary<User, Account, ApiKeyPair> keyPairTernary = findUserByApiKey(apiKey);
if (keyPairTernary != null) {
throw new InvalidParameterValueException(String.format("The API key [%s] already exists in the system. Please provide a unique key.", apiKey));
}
lastUserKeyPair.setApiKey(apiKey);
lastUserKeyPair.setSecretKey(secretKey);
apiKeyPairDao.update(lastUserKeyPair.getId(), lastUserKeyPair);
}
protected void validateAndUpdateUserApiKeyAccess(UpdateUserCmd updateUserCmd, UserVO user) {
@ -2418,6 +2478,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// don't allow to delete the user from the account of type Project
checkAccountAndAccess(user, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
removeUserApiKeys(id);
return _userDao.remove(id);
}
@ -2457,8 +2520,6 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
UserVO newUser = new UserVO(user);
user.setExternalEntity(user.getUuid());
user.setUuid(UUID.randomUUID().toString());
user.setApiKey(null);
user.setSecretKey(null);
_userDao.update(user.getId(), user);
newUser.setAccountId(newAccountId);
boolean success = _userDao.remove(user.getId());
@ -3094,37 +3155,46 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
}
@Override
public Pair<User, Account> findUserByApiKey(String apiKey) {
UserAccount userAccount = userAccountDao.getUserByApiKey(apiKey);
if (userAccount != null) {
User user = _userDao.getUser(userAccount.getId());
Account account = _accountDao.findById(userAccount.getAccountId());
return new Pair<>(user, account);
} else {
public Ternary<User, Account, ApiKeyPair> findUserByApiKey(String apiKey) {
ApiKeyPairVO keyPairVO = apiKeyPairDao.findByApiKey(apiKey);
if (keyPairVO == null) {
return null;
}
User user = _userDao.getUser(keyPairVO.getUserId());
Account account = _accountDao.findById(keyPairVO.getAccountId());
return new Ternary<>(user, account, keyPairVO);
}
@Override
public Pair<Boolean, Map<String, String>> getKeys(GetUserKeysCmd cmd) {
final long userId = cmd.getID();
return getKeys(userId);
}
@Override
public Pair<Boolean, Map<String, String>> getKeys(Long userId) {
final long userId = cmd.getId();
User user = getActiveUser(userId);
if (user == null) {
throw new InvalidParameterValueException("Unable to find user by id");
throw new InvalidParameterValueException(String.format("Unable to find active user with ID [%s].", userId));
}
final Account account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user.
final Account account = getAccount(user.getAccountId());
User caller = CallContext.current().getCallingUser();
checkAccess(caller, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
String accessingApiKey = getAccessingApiKey(cmd);
ApiKeyPair keyPair;
if (accessingApiKey != null) {
ApiKeyPair accessingKeyPair = apiKeyPairService.findByApiKey(accessingApiKey);
if (userId == accessingKeyPair.getUserId()) {
keyPair = apiKeyPairService.findByApiKey(accessingApiKey);
} else {
keyPair = _accountService.getLatestUserKeyPair(userId);
}
} else {
keyPair = _accountService.getLatestUserKeyPair(userId);
}
Map<String, String> keys = new HashMap<>();
keys.put("apikey", user.getApiKey());
keys.put("secretkey", user.getSecretKey());
boolean isAllowed = keyPair != null && isAccessingKeypairSuperset(keyPair, cmd);
keys.put("apikey", isAllowed ? keyPair.getApiKey() : null);
keys.put("secretkey", isAllowed ? keyPair.getSecretKey() : null);
Boolean apiKeyAccess = user.getApiKeyAccess();
if (apiKeyAccess == null) {
@ -3137,6 +3207,206 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return new Pair<>(apiKeyAccess, keys);
}
@Override
public ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd) {
ListResponse<ApiKeyPairResponse> finalResponse = new ListResponse<>();
List<ApiKeyPairResponse> responses = new ArrayList<>();
if (cmd.getKeyId() != null || cmd.getApiKeyFilter() != null) {
fetchOnlyOneKeyPair(responses, cmd);
finalResponse.setResponses(responses);
return finalResponse;
}
Integer total = fetchMultipleKeyPairs(responses, cmd);
finalResponse.setResponses(responses, total);
return finalResponse;
}
private void fetchOnlyOneKeyPair(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
ApiKeyPair keyPair;
if (cmd.getKeyId() != null) {
keyPair = _accountService.getKeyPairById(cmd.getKeyId());
} else {
keyPair = _accountService.getKeyPairByApiKey(cmd.getApiKeyFilter());
}
validateKeyPairIsNotNull(keyPair);
validateAccessingKeyPairPermissionsIsSupersetOfAccessedKeyPair(keyPair, cmd);
validateAccessToApiKey(keyPair);
removeApiKeyPairIfExpired(keyPair);
addKeypairResponse(keyPair, responses, cmd);
}
private void validateAccessToApiKey(ApiKeyPair keyPair) {
Account caller = getCurrentCallingAccount();
logger.debug("Verifying if caller [{}] has access to API key pair whose ID is [{}].", caller.getAccountName(), keyPair.getUuid());
Account account = _accountDao.findById(keyPair.getAccountId());
checkAccess(caller, null, false, account);
_accountService.validateCallingUserHasAccessToDesiredUser(keyPair.getUserId());
}
private Integer fetchMultipleKeyPairs(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
List<Long> users;
if (cmd.getUserId() != null) {
_accountService.validateCallingUserHasAccessToDesiredUser(cmd.getUserId());
users = List.of(cmd.getUserId());
} else {
User callerUser = CallContext.current().getCallingUser();
users = cmd.getListAll() && isAdmin(callerUser.getAccountId()) ? queryService.searchForAccessibleUsers() : List.of(callerUser.getId());
}
Pair<List<ApiKeyPairVO>, Integer> keyPairs = apiKeyPairDao.listByUserIdsPaginated(users, cmd);
keyPairs.first().stream()
.filter(keyPair -> isAccessingKeypairSuperset(keyPair, cmd))
.forEach(keyPair -> {
addKeypairResponse(keyPair, responses, cmd);
removeApiKeyPairIfExpired(keyPair);
});
return keyPairs.second();
}
@Override
public List<ApiKeyPairPermission> listKeyRules(ListUserKeyRulesCmd cmd) {
ApiKeyPair keyPair = apiKeyPairService.findById(cmd.getId());
validateKeyPairIsNotNull(keyPair);
validateAccessingKeyPairPermissionsIsSupersetOfAccessedKeyPair(keyPair, cmd);
_accountService.validateCallingUserHasAccessToDesiredUser(keyPair.getUserId());
Account account = _accountDao.findById(keyPair.getAccountId());
return apiKeyPairService.findAllPermissionsByKeyPairId(keyPair.getId(), account.getRoleId());
}
private void validateKeyPairIsNotNull(ApiKeyPair keyPair) {
if (keyPair == null) {
logger.info("Keypair not found.");
throw new InvalidParameterValueException("Could not complete request.");
}
}
private void validateAccessingKeyPairPermissionsIsSupersetOfAccessedKeyPair(ApiKeyPair keyPair, BaseCmd cmd) {
if (!isAccessingKeypairSuperset(keyPair, cmd)) {
logger.info("Accessing API key pair [{}] has less permissions than accessed API key pair.", keyPair.getId());
throw new PermissionDeniedException("Could not complete request.");
}
}
private Boolean isAccessingKeypairSuperset(ApiKeyPair accessedKeyPair, BaseCmd cmd) {
String apiKey = getAccessingApiKey(cmd);
if (apiKey == null) {
return Boolean.TRUE;
}
ApiKeyPair accessingKeyPair = apiKeyPairService.findByApiKey(apiKey);
return isApiKeySupersetOfPermission(new ArrayList<>(getAllKeypairPermissions(accessingKeyPair.getApiKey())), new ArrayList<>(getAllKeypairPermissions(accessedKeyPair.getApiKey())));
}
@Override
public String getAccessingApiKey(BaseCmd cmd) {
try {
if (cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd) cmd).getJob().toString().contains("\"signature\"")) {
return parseApiKeyFromAsyncJob((BaseAsyncCmd) cmd);
}
boolean accessedByApiKey = cmd.getFullUrlParams().containsKey(ApiConstants.SIGNATURE);
String accessingApiKey = cmd.getFullUrlParams().get("apiKey");
if (accessedByApiKey) {
return accessingApiKey;
}
} catch (NullPointerException e) {
logger.info("Accessing API through session.");
}
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));
}
private Boolean isApiKeySupersetOfPermission(List<RolePermissionEntity> baseKeyPairPermissions, List<RolePermissionEntity> comparedPermissions) {
Map<String, RolePermissionEntity> apiNameToBaseKeyPermissions = roleService.getRoleRulesAndPermissions(baseKeyPairPermissions);
return roleService.roleHasPermission(apiNameToBaseKeyPermissions, comparedPermissions);
}
private void removeApiKeyPairIfExpired(ApiKeyPair apiKeyPair) {
if (apiKeyPair.hasEndDatePassed()) {
internalDeleteApiKey(apiKeyPair);
}
}
public void deleteApiKey(DeleteUserKeysCmd cmd) {
ApiKeyPair keyPair = apiKeyPairService.findById(cmd.getId());
if (keyPair == null) {
throw new InvalidParameterValueException(String.format("No keypair found with the ID [%s].", cmd.getId()));
}
_accountService.validateCallingUserHasAccessToDesiredUser(keyPair.getUserId());
deleteApiKey(keyPair);
}
@Override
public void validateCallingUserHasAccessToDesiredUser(Long userId) {
User desiredUser = _userDao.getUser(userId);
if (desiredUser == null) {
throw new CloudRuntimeException(String.format("Unable to find user with ID [%s].", userId));
}
verifyCallerPrivilegeForUserOrAccountOperations(desiredUser);
}
@Override
public void deleteApiKey(ApiKeyPair keyPair) {
User user = _userDao.findByIdIncludingRemoved(keyPair.getUserId());
if (user == null) {
throw new InvalidParameterValueException("User associated with the API key pair does not exist.");
}
if ((BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername()) || user.getId() == User.UID_SYSTEM)
&& Boolean.parseBoolean(_configDao.getValue(Config.BaremetalProvisionDoneNotificationEnabled.key()))) {
throw new PermissionDeniedException(String.format("User ID [%s] is a system account and the global setting " +
"baremetal.provision.done.notification is enabled. Therefore, it is not possible to delete API key pairs. If you wish to delete " +
"the baremetal user/account or their API Key, please disable the baremetal.provision.done.notification configuration.", user.getUuid()));
}
internalDeleteApiKey(keyPair);
}
private void internalDeleteApiKey(ApiKeyPair keyPair) {
List<ApiKeyPairPermissionVO> permissions = apiKeyPairPermissionsDao.findAllByApiKeyPairId(keyPair.getId());
for (ApiKeyPairPermission permission : permissions) {
apiKeyPairPermissionsDao.remove(permission.getId());
}
apiKeyPairDao.remove(keyPair.getId());
}
private void addKeypairResponse(ApiKeyPair keyPair, List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
if (keyPair == null) {
return;
}
ApiKeyPairResponse response = cmd._responseGenerator.createKeyPairResponse(keyPair);
if (Boolean.TRUE.equals(cmd.getShowPermissions())) {
Account account = _accountDao.findById(keyPair.getAccountId());
List<ApiKeyPairPermission> apiKeyPairPermissions = apiKeyPairService.findAllPermissionsByKeyPairId(keyPair.getId(), account.getRoleId());
response.setPermissions(apiKeyPairPermissions.stream().map(apiKeyPairPermission -> {
BaseRolePermissionResponse rolePermissionResponse = new BaseRolePermissionResponse();
rolePermissionResponse.setRule(apiKeyPairPermission.getRule());
rolePermissionResponse.setDescription(apiKeyPairPermission.getDescription());
rolePermissionResponse.setRulePermission(apiKeyPairPermission.getPermission());
return rolePermissionResponse;
}).collect(Collectors.toList()));
}
response.setObjectName(ApiConstants.USER_API_KEY);
responses.add(response);
}
@Override
public ApiKeyPair getKeyPairById(Long id) {
return apiKeyPairDao.findById(id);
}
protected void preventRootDomainAdminAccessToRootAdminKeys(User caller, ControlledEntity account) {
if (isDomainAdminForRootDomain(caller) && isRootAdmin(account.getAccountId())) {
String msg = String.format("Caller Username %s does not have access to root admin keys", caller.getUsername());
@ -3150,6 +3420,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return caller.getType() == Account.Type.DOMAIN_ADMIN && caller.getDomainId() == Domain.ROOT_DOMAIN;
}
@Override
public ApiKeyPair getKeyPairByApiKey(String apiKey) {
return apiKeyPairDao.findByApiKey(apiKey);
}
@Override
public List<UserTwoFactorAuthenticator> listUserTwoFactorAuthenticationProviders() {
return userTwoFactorAuthenticationProviders;
@ -3174,39 +3449,43 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY, eventDescription = "register for the developer API keys")
public String[] createApiKeyAndSecretKey(RegisterUserKeyCmd cmd) {
public ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd cmd) {
Account caller = getCurrentCallingAccount();
final Long userId = cmd.getId();
User user = getUserIncludingRemoved(userId);
User user = _userDao.findById(cmd.getUserId());
if (user == null) {
throw new InvalidParameterValueException("unable to find user by id");
throw new InvalidParameterValueException(String.format("Unable to find user by ID: %d", cmd.getUserId()));
}
final String name = cmd.getName();
final long userId = user.getId();
final String description = cmd.getDescription();
final Date startDate = cmd.getStartDate();
final Date endDate = cmd.getEndDate();
final List<Map<String, Object>> rules = cmd.getRules();
Account account = _accountDao.findById(user.getAccountId());
checkAccess(caller, null, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
// don't allow updating system user
if (user.getId() == User.UID_SYSTEM) {
throw new PermissionDeniedException(String.format("user: %s is system account, update is not allowed", user));
}
// don't allow baremetal system user
if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername())) {
throw new PermissionDeniedException(String.format("user: %s is system account, update is not allowed", user));
if (BaremetalUtils.BAREMETAL_SYSTEM_ACCOUNT_NAME.equals(user.getUsername()) || user.getId() == User.UID_SYSTEM) {
throw new PermissionDeniedException(String.format("User ID: [%s] is a system account and, thus, the operation is not allowed.", user.getId()));
}
// generate both an api key and a secret key, update the user table with the keys, return the keys to the user
final String[] keys = new String[2];
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
keys[0] = createUserApiKey(userId);
keys[1] = createUserSecretKey(userId);
}
Date now = DateTime.now().toDate();
if (endDate != null && endDate.compareTo(now) <= 0) {
throw new InvalidParameterValueException("Keypair cannot be created with expired date, please input a date in the future.");
}
if (ObjectUtils.allNotNull(startDate, endDate) && startDate.compareTo(endDate) > -1) {
throw new InvalidParameterValueException("Please specify an end date that is after the start date.");
}
final ApiKeyPairVO newApiKeyPair = new ApiKeyPairVO(name, userId, description, startDate, endDate, account);
return Transaction.execute((TransactionCallback<ApiKeyPairVO>) status -> {
createUserApiKey(userId, newApiKeyPair);
createUserSecretKey(userId, newApiKeyPair);
return validateAndPersistKeyPairAndPermissions(account, newApiKeyPair, rules, cmd);
});
return keys;
}
@Override
@ -3221,37 +3500,91 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
Account account = _accountDao.findById(user.getAccountId());
checkAccess(caller, null, true, account);
final String[] keys = new String[2];
ApiKeyPairVO newTokenKeyPair = new ApiKeyPairVO();
newTokenKeyPair.setName(String.valueOf(userId));
newTokenKeyPair.setAccountId(user.getAccountId());
newTokenKeyPair.setDomainId(account.getDomainId());
newTokenKeyPair.setUserId(userId);
Transaction.execute(new TransactionCallbackNoReturn() {
@Override
public void doInTransactionWithoutResult(TransactionStatus status) {
keys[0] = AccountManagerImpl.this.createUserApiKey(userId);
keys[1] = AccountManagerImpl.this.createUserSecretKey(userId);
keys[0] = createUserApiKey(userId, newTokenKeyPair);
keys[1] = createUserSecretKey(userId, newTokenKeyPair);
apiKeyPairDao.persist(newTokenKeyPair);
}
});
return keys;
}
private String createUserApiKey(long userId) {
try {
UserVO updatedUser = _userDao.createForUpdate();
/**
* Persists the API key pair and its corresponding permissions. Verifies whether
* the key pair being created is a superset of its owner's permissions.
* @param account Account owner of the key pair.
* @param newApiKeyPair The key pair object to be persisted.
* @param rules The set of rules of the key pair.
* @param cmd The API's command.
* @return The persisted key pair object.
*/
@DB
private ApiKeyPairVO validateAndPersistKeyPairAndPermissions(Account account, ApiKeyPairVO newApiKeyPair,
List<Map<String, Object>> rules, RegisterUserKeysCmd cmd) {
String accessingApiKey = getAccessingApiKey(cmd);
final Role accountRole = roleService.findRole(account.getRoleId());
List<RolePermissionEntity> allPermissions = accessingApiKey == null ?
roleService.findAllRolePermissionsEntityBy(accountRole.getId(), true) : getAllKeypairPermissions(accessingApiKey);
List<RolePermissionEntity> permissions = new ArrayList<>();
for (Map<String, Object> ruleDetail : rules) {
String rule = ruleDetail.get(ApiConstants.RULE).toString();
RolePermission.Permission rulePermission = (RolePermission.Permission) ruleDetail.get(ApiConstants.PERMISSION);
String ruleDescription = (String) ruleDetail.get(ApiConstants.DESCRIPTION);
permissions.add(new ApiKeyPairPermissionVO(0, rule, rulePermission, ruleDescription));
}
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()));
}
ApiKeyPairVO savedApiKeyPair = apiKeyPairDao.persist(newApiKeyPair);
permissions.forEach(permission -> {
ApiKeyPairPermissionVO permissionVO = (ApiKeyPairPermissionVO) permission;
permissionVO.setApiKeyPairId(savedApiKeyPair.getId());
apiKeyPairPermissionsDao.persist(permissionVO);
});
return savedApiKeyPair;
}
@Override
public List<RolePermissionEntity> getAllKeypairPermissions(String apiKey) {
if (apiKey == null) {
throw new InvalidParameterValueException("API key not present in the request's URL and, thus, unable to fetch API key rules.");
}
ApiKeyPair apiKeyPair = keyPairManager.findByApiKey(apiKey);
Account account = _accountDao.findById(apiKeyPair.getAccountId());
List<ApiKeyPairPermission> keyPairPermissions = keyPairManager.findAllPermissionsByKeyPairId(apiKeyPair.getId(), account.getRoleId());
return new ArrayList<>(keyPairPermissions);
}
private String createUserApiKey(long userId, ApiKeyPairVO newApiKeyPair) {
try {
String encodedKey;
UserAccount userAcct;
ApiKeyPair keyPair;
int retryLimit = 10;
do {
// FIXME: what algorithm should we use for API keys?
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
SecretKey key = generator.generateKey();
encodedKey = Base64.encodeBase64URLSafeString(key.getEncoded());
userAcct = userAccountDao.getUserByApiKey(encodedKey);
keyPair = apiKeyPairDao.findByApiKey(encodedKey);
retryLimit--;
} while ((userAcct != null) && (retryLimit >= 0));
} while ((keyPair != null) && (retryLimit >= 0));
if (userAcct != null) {
if (keyPair != null) {
return null;
}
updatedUser.setApiKey(encodedKey);
_userDao.update(userId, updatedUser);
newApiKeyPair.setApiKey(encodedKey);
return encodedKey;
} catch (NoSuchAlgorithmException ex) {
logger.error("error generating secret key for user {}", userAccountDao.findById(userId), ex);
@ -3259,26 +3592,24 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return null;
}
private String createUserSecretKey(long userId) {
private String createUserSecretKey(long userId, ApiKeyPairVO newApiKeyPair) {
try {
UserVO updatedUser = _userDao.createForUpdate();
String encodedKey;
int retryLimit = 10;
UserVO userBySecretKey;
ApiKeyPairVO keyPairVO;
do {
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
SecretKey key = generator.generateKey();
encodedKey = Base64.encodeBase64URLSafeString(key.getEncoded());
userBySecretKey = _userDao.findUserBySecretKey(encodedKey);
keyPairVO = apiKeyPairDao.findBySecretKey(encodedKey);
retryLimit--;
} while ((userBySecretKey != null) && (retryLimit >= 0));
} while ((keyPairVO != null) && (retryLimit >= 0));
if (userBySecretKey != null) {
if (keyPairVO != null) {
return null;
}
updatedUser.setSecretKey(encodedKey);
_userDao.update(userId, updatedUser);
newApiKeyPair.setSecretKey(encodedKey);
return encodedKey;
} catch (NoSuchAlgorithmException ex) {
logger.error("error generating secret key for user {}", userAccountDao.findById(userId), ex);
@ -3286,6 +3617,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return null;
}
public ApiKeyPair getLatestUserKeyPair(Long userId) {
return ApiDBUtils.searchForLatestUserKeyPair(userId);
}
@Override
public void buildACLSearchBuilder(SearchBuilder<? extends ControlledEntity> sb, Long domainId, boolean isRecursive, List<Long> permittedAccounts,
ListProjectResourcesCriteria listProjectResourcesCriteria) {
@ -3492,7 +3827,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
public UserAccount getUserByApiKey(String apiKey) {
return userAccountDao.getUserByApiKey(apiKey);
ApiKeyPairVO keyPair = apiKeyPairDao.findByApiKey(apiKey);
return userAccountDao.findById(keyPair.getUserId());
}
@Override
@ -3553,9 +3889,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
public UserAccount getUserAccountById(Long userId) {
UserAccount userAccount = userAccountDao.findById(userId);
Map<String, String> details = _userDetailsDao.listDetailsKeyPairs(userId);
userAccount.setDetails(details);
if (userAccount != null) {
Map<String, String> details = _userDetailsDao.listDetailsKeyPairs(userId);
userAccount.setDetails(details);
}
return userAccount;
}

View File

@ -60,6 +60,7 @@ import javax.naming.ConfigurationException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import com.cloud.serializer.GsonHelper;
import com.cloud.storage.SnapshotPolicyVO;
import com.cloud.storage.dao.SnapshotPolicyDao;
import org.apache.cloudstack.acl.ControlledEntity;
@ -132,6 +133,7 @@ import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
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.managed.context.ManagedContextRunnable;
@ -651,6 +653,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
protected static long ROOT_DEVICE_ID = 0;
private final Type jobParamsType = new TypeToken<HashMap<String, String>>() {}.getType();
public List<KubernetesServiceHelper> getKubernetesServiceHelpers() {
return kubernetesServiceHelpers;
}
@ -3459,14 +3463,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
return AllowUserExpungeRecoverVm.valueIn(accountId);
}
protected void checkExpungeVmPermission (Account callingAccount) {
protected void checkExpungeVmPermission(Account callingAccount, String apiKey) {
logger.debug(String.format("Checking if [%s] has permission for expunging VMs.", callingAccount));
if (!_accountMgr.isAdmin(callingAccount.getId()) && !getConfigAllowUserExpungeRecoverVm(callingAccount.getId())) {
logger.error(String.format("Parameter [%s] can only be passed by Admin accounts or when the allow.user.expunge.recover.vm key is true.", ApiConstants.EXPUNGE));
throw new PermissionDeniedException("Account does not have permission for expunging.");
}
try {
_accountMgr.checkApiAccess(callingAccount, BaseCmd.getCommandNameByClass(ExpungeVMCmd.class));
_accountMgr.checkApiAccess(callingAccount, BaseCmd.getCommandNameByClass(ExpungeVMCmd.class), apiKey);
} catch (PermissionDeniedException ex) {
logger.error(String.format("Role [%s] of [%s] does not have permission for expunging VMs.", callingAccount.getRoleId(), callingAccount));
throw new PermissionDeniedException("Account does not have permission for expunging.");
@ -3491,7 +3495,10 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
boolean expunge = cmd.getExpunge();
if (expunge) {
checkExpungeVmPermission(ctx.getCallingAccount());
String jobParamsString = ((AsyncJobVO) cmd.getJob()).getCmdInfo();
HashMap<String,String> jobParams = GsonHelper.getGson().fromJson(jobParamsString, jobParamsType);
String apiKey = jobParams.get("apiKey");
checkExpungeVmPermission(ctx.getCallingAccount(), apiKey);
}
// check if VM exists

View File

@ -0,0 +1,87 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.acl;
import com.cloud.utils.component.ManagerBase;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission;
import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
import org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDao;
import org.apache.commons.collections.CollectionUtils;
import javax.inject.Inject;
import java.util.List;
public class ApiKeyPairManagerImpl extends ManagerBase implements ApiKeyPairService {
@Inject
private ApiKeyPairDao apiKeyPairDao;
@Inject
private ApiKeyPairPermissionsDao apiKeyPairPermissionsDao;
@Inject
private RoleService roleService;
@Override
public List<ApiKeyPairPermission> findAllPermissionsByKeyPairId(Long apiKeyPairId, Long roleId) {
List<ApiKeyPairPermissionVO> keyPairPermissions = apiKeyPairPermissionsDao.findAllByKeyPairIdSorted(apiKeyPairId);
List<RolePermissionEntity> rolePermissions = roleService.findAllRolePermissionsEntityBy(roleId, true);
if (CollectionUtils.isEmpty(keyPairPermissions)) {
return rolePermissions.stream()
.map(rolePermission -> new ApiKeyPairPermissionVO(rolePermission.getRule().getRuleString(), rolePermission.getPermission(), rolePermission.getDescription()))
.collect(Collectors.toList());
}
Map<String, RolePermissionEntity> rolePermissionInfo = roleService.getRoleRulesAndPermissions(rolePermissions);
if (roleService.roleHasPermission(rolePermissionInfo, new ArrayList<>(keyPairPermissions))) {
return new ArrayList<>(keyPairPermissions);
}
Map<String, RolePermissionEntity> keyPairPermissionInfo = roleService.getRoleRulesAndPermissions(new ArrayList<>(keyPairPermissions));
return getRulesToBeKeptForTheKeyPair(rolePermissionInfo, keyPairPermissionInfo)
.entrySet().stream().map((permission) -> new ApiKeyPairPermissionVO(permission.getKey(), permission.getValue().getPermission(), permission.getValue().getDescription()))
.collect(Collectors.toList());
}
private Map<String, RolePermissionEntity> getRulesToBeKeptForTheKeyPair(Map<String, RolePermissionEntity> rolePermissions, Map<String, RolePermissionEntity> keyPairPermissions) {
Map<String, RolePermissionEntity> rulesToBeKept = new HashMap<>();
for (Map.Entry<String, RolePermissionEntity> keyPairPermission : keyPairPermissions.entrySet()) {
String rule = keyPairPermission.getKey();
RolePermissionEntity permission = keyPairPermission.getValue();
boolean permissionGrantedByRole = rolePermissions.containsKey(rule) && rolePermissions.get(rule).getPermission() == RolePermissionEntity.Permission.ALLOW;
if (permission.getPermission() == RolePermissionEntity.Permission.ALLOW && permissionGrantedByRole) {
rulesToBeKept.put(rule, permission);
}
}
return rulesToBeKept;
}
@Override
public ApiKeyPair findByApiKey(String apiKey) {
return apiKeyPairDao.findByApiKey(apiKey);
}
@Override
public ApiKeyPair findById(Long id) {
return apiKeyPairDao.findById(id);
}
}

View File

@ -75,6 +75,7 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
private RolePermissionsDao rolePermissionsDao;
@Inject
private AccountManager accountManager;
private List<APIChecker> apiAccessCheckers;
public void checkCallerAccess() {
if (!isEnabled()) {
@ -449,20 +450,21 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
/**
* Removes roles from the given list if the role has different or more permissions than the user's calling the method role
*/
protected int removeRolesIfNeeded(List<? extends Role> roles) {
@Override
public int removeRolesIfNeeded(List<? extends Role> roles) {
if (roles.isEmpty()) {
return 0;
}
Long callerRoleId = getCurrentAccount().getRoleId();
Map<String, Permission> callerRolePermissions = getRoleRulesAndPermissions(callerRoleId);
Map<String, RolePermissionEntity> callerRolePermissions = getRoleRulesAndPermissions(findAllRolePermissionsEntityBy(callerRoleId, false));
int count = 0;
Iterator<? extends Role> rolesIterator = roles.iterator();
while (rolesIterator.hasNext()) {
Role role = rolesIterator.next();
if (role.getId() == callerRoleId || roleHasPermission(callerRolePermissions, role)) {
if (role.getId() == callerRoleId || roleHasPermission(callerRolePermissions, findAllRolePermissionsEntityBy(role.getId(), false))) {
continue;
}
@ -473,17 +475,11 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
return count;
}
/**
* Checks if the role of the caller account has compatible permissions of the specified role.
* For each permission of the role of the caller, the target role needs to contain the same permission.
*
* @param sourceRolePermissions the permissions of the caller role.
* @param targetRole the role that the caller role wants to access.
* @return True if the role can be accessed with the given permissions; false otherwise.
*/
protected boolean roleHasPermission(Map<String, Permission> sourceRolePermissions, Role targetRole) {
@Override
public boolean roleHasPermission(Map<String, RolePermissionEntity> rolePermissions, List<RolePermissionEntity> rolePermissionsToAccess) {
Set<String> rulesAlreadyCompared = new HashSet<>();
for (RolePermission rolePermission : findAllPermissionsBy(targetRole.getId())) {
for (RolePermissionEntity rolePermission : rolePermissionsToAccess) {
boolean permissionIsRegex = rolePermission.getRule().getRuleString().contains("*");
for (String apiName : accountManager.getApiNameList()) {
@ -491,7 +487,7 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
continue;
}
if (rolePermission.getPermission() == Permission.ALLOW && (!sourceRolePermissions.containsKey(apiName) || sourceRolePermissions.get(apiName) == Permission.DENY)) {
if (rolePermission.getPermission() == Permission.ALLOW && (!rolePermissions.containsKey(apiName) || rolePermissions.get(apiName).getPermission() == Permission.DENY)) {
return false;
}
@ -506,34 +502,34 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
return true;
}
/**
* 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.
*
* @param roleId ID from role.
*/
public Map<String, Permission> getRoleRulesAndPermissions(Long roleId) {
Map<String, Permission> roleRulesAndPermissions = new HashMap<>();
@Override
public Map<String, RolePermissionEntity> getRoleRulesAndPermissions(List<RolePermissionEntity> rolePermissions) {
Map<String, RolePermissionEntity> roleRulesAndPermissions = new HashMap<>();
for (RolePermission rolePermission : findAllPermissionsBy(roleId)) {
for (RolePermissionEntity rolePermission : rolePermissions) {
boolean permissionIsRegex = rolePermission.getRule().getRuleString().contains("*");
for (String apiName : accountManager.getApiNameList()) {
if (!rolePermission.getRule().matches(apiName)) {
continue;
}
if (!roleRulesAndPermissions.containsKey(apiName)) {
roleRulesAndPermissions.put(apiName, rolePermission.getPermission());
}
if (!permissionIsRegex) {
break;
}
}
mapRolePermissionToApiNames(rolePermission, roleRulesAndPermissions, permissionIsRegex);
}
return roleRulesAndPermissions;
}
private void mapRolePermissionToApiNames(RolePermissionEntity rolePermission, Map<String, RolePermissionEntity> roleRulesAndPermissions, boolean permissionIsRegex) {
for (String apiName : accountManager.getApiNameList()) {
if (!rolePermission.getRule().matches(apiName)) {
continue;
}
if (!roleRulesAndPermissions.containsKey(apiName)) {
roleRulesAndPermissions.put(apiName, rolePermission);
}
if (!permissionIsRegex) {
break;
}
}
}
@Override
public List<Role> findRolesByType(RoleType roleType) {
return findRolesByType(roleType, null, null, null).first();
@ -571,6 +567,43 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
return Collections.emptyList();
}
@Override
public List<RolePermissionEntity> findAllRolePermissionsEntityBy(final Long roleId, final boolean considerImplicitRules) {
List<RolePermissionVO> rolePermissions = rolePermissionsDao.findAllByRoleIdSorted(roleId);
if (!considerImplicitRules) {
return new ArrayList<>(rolePermissions);
}
List<RolePermissionEntity> permissions = new ArrayList<>();
List<RolePermissionEntity> implicitPermissions = getImplicitRolePermissions(roleId);
for (RolePermissionEntity implicitPermission : implicitPermissions) {
boolean implicitPermissionAlreadyDefinedByExplicitPermissions = rolePermissions.stream()
.anyMatch(permission -> permission.getRule().matches(implicitPermission.getRule().getRuleString()));
if (!implicitPermissionAlreadyDefinedByExplicitPermissions) {
permissions.add(implicitPermission);
}
}
permissions.addAll(rolePermissions);
return permissions;
}
private List<RolePermissionEntity> getImplicitRolePermissions(Long roleId) {
Role role = roleDao.findById(roleId);
if (role == null) {
return List.of();
}
for (APIChecker apiChecker : apiAccessCheckers) {
List<RolePermissionEntity> implicitPermissions = apiChecker.getImplicitRolePermissions(role.getRoleType());
if (apiChecker.isEnabled() && !CollectionUtils.isEmpty(implicitPermissions)) {
return implicitPermissions;
}
}
return List.of();
}
private boolean isCallerRootAdmin() {
return accountManager.isRootAdmin(getCurrentAccount().getId());
}
@ -613,4 +646,9 @@ public class RoleManagerImpl extends ManagerBase implements RoleService, Configu
cmdList.add(DisableRoleCmd.class);
return cmdList;
}
@Inject
public void setApiAccessCheckers(List<APIChecker> apiAccessCheckers) {
this.apiAccessCheckers = apiAccessCheckers;
}
}

View File

@ -37,10 +37,14 @@
value="#{pluggableAPIAuthenticatorsRegistry.registered}" />
</bean>
<bean id="roleManagerImpl" class="org.apache.cloudstack.acl.RoleManagerImpl" />
<bean id="roleManagerImpl" class="org.apache.cloudstack.acl.RoleManagerImpl">
<property name="apiAccessCheckers" value="#{apiAclCheckersRegistry.registered}" />
</bean>
<bean id="projRoleManagerImpl" class="org.apache.cloudstack.acl.ProjectRoleManagerImpl" />
<bean id="ApiKeyPairManagerImpl" class="org.apache.cloudstack.acl.ApiKeyPairManagerImpl" />
<bean id="accountManagerImpl" class="com.cloud.user.AccountManagerImpl">
<property name="userAuthenticators"
value="#{userAuthenticatorsRegistry.registered}" />

View File

@ -76,6 +76,9 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import com.cloud.api.ApiDBUtils;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import com.cloud.agent.AgentManager;
import com.cloud.agent.api.PerformanceMonitorAnswer;
import com.cloud.agent.api.PerformanceMonitorCommand;
@ -266,9 +269,14 @@ public class AutoScaleManagerImplTest {
@Mock
GuestOSDao guestOSDao;
@Mock
NetworkOrchestrationService networkOrchestrationService;
AccountVO account;
UserVO user;
MockedStatic<ApiDBUtils> mockedApiDBUtils;
final static String INVALID = "invalid";
private static final Long counterId = 1L;
@ -419,6 +427,11 @@ public class AutoScaleManagerImplTest {
Mockito.doNothing().when(accountManager).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
when(asPolicyDao.persist(any(AutoScalePolicyVO.class))).thenReturn(asScaleUpPolicyMock);
mockedApiDBUtils = Mockito.mockStatic(ApiDBUtils.class);
ApiKeyPairVO ret = new ApiKeyPairVO();
ret.setSecretKey("secretkey");
ret.setApiKey("apikey");
when(ApiDBUtils.searchForLatestUserKeyPair(Mockito.any())).thenReturn(ret);
userDataDetails.put("0", new HashMap<>() {{ put("key1", "value1"); put("key2", "value2"); }});
Mockito.doReturn(userDataFinal).when(userVmMgr).finalizeUserData(any(), any(), any());
@ -432,6 +445,7 @@ public class AutoScaleManagerImplTest {
@After
public void tearDown() {
mockedApiDBUtils.close();
CallContext.unregister();
}
@ -879,9 +893,6 @@ public class AutoScaleManagerImplTest {
public void testCheckAutoScaleUserSucceed() throws NoSuchFieldException, IllegalAccessException {
when(userDao.findById(any())).thenReturn(userMock);
when(userMock.getAccountId()).thenReturn(accountId);
when(userMock.getApiKey()).thenReturn(autoScaleUserApiKey);
when(userMock.getSecretKey()).thenReturn(autoScaleUserSecretKey);
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);
f.set(ApiServiceConfiguration.ApiServletPath, "http://10.10.10.10:8080/client/api");
@ -891,25 +902,6 @@ public class AutoScaleManagerImplTest {
@Test(expected = InvalidParameterValueException.class)
public void testCheckAutoScaleUserFail1() {
when(userDao.findById(any())).thenReturn(userMock);
when(userMock.getAccountId()).thenReturn(accountId);
when(userMock.getApiKey()).thenReturn(autoScaleUserApiKey);
when(userMock.getSecretKey()).thenReturn(null);
autoScaleManagerImplSpy.checkAutoScaleUser(autoScaleUserId, accountId);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckAutoScaleUserFail2() {
when(userDao.findById(any())).thenReturn(userMock);
when(userMock.getAccountId()).thenReturn(accountId);
when(userMock.getApiKey()).thenReturn(null);
autoScaleManagerImplSpy.checkAutoScaleUser(autoScaleUserId, accountId);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckAutoScaleUserFail3() {
when(userDao.findById(any())).thenReturn(userMock);
when(userMock.getAccountId()).thenReturn(accountId + 1L);
@ -917,18 +909,16 @@ public class AutoScaleManagerImplTest {
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckAutoScaleUserFail4() {
public void testCheckAutoScaleUserFail2() {
when(userDao.findById(any())).thenReturn(null);
autoScaleManagerImplSpy.checkAutoScaleUser(autoScaleUserId, accountId);
}
@Test(expected = InvalidParameterValueException.class)
public void testCheckAutoScaleUserFail5() throws NoSuchFieldException, IllegalAccessException {
public void testCheckAutoScaleUserFail3() throws NoSuchFieldException, IllegalAccessException {
when(userDao.findById(any())).thenReturn(userMock);
when(userMock.getAccountId()).thenReturn(accountId);
when(userMock.getApiKey()).thenReturn(autoScaleUserApiKey);
when(userMock.getSecretKey()).thenReturn(autoScaleUserSecretKey);
final Field f = ConfigKey.class.getDeclaredField("_defaultValue");
f.setAccessible(true);

View File

@ -19,12 +19,15 @@ package com.cloud.user;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable;
import com.cloud.utils.Ternary;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -32,10 +35,24 @@ import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
import org.apache.cloudstack.acl.ApiKeyPairVO;
import org.apache.cloudstack.acl.RolePermission;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RolePermissionVO;
import org.apache.cloudstack.acl.RoleVO;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPair;
import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService;
import org.apache.cloudstack.acl.dao.ApiKeyPairDao;
import org.apache.cloudstack.acl.dao.ApiKeyPairPermissionsDao;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.response.UserTwoFactorAuthenticationSetupResponse;
import org.apache.cloudstack.auth.UserAuthenticator;
@ -86,7 +103,9 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
@Mock
private AccountService accountService;
@Mock
private GetUserKeysCmd _listkeyscmd;
private GetUserKeysCmd _getkeyscmd;
@Mock
private ListUserKeysCmd listUserKeysCmd;
@Mock
private User _user;
@Mock
@ -102,6 +121,15 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
@Mock
private UserVO userVoMock;
@Mock
private ApiKeyPairService apiKeyPairService;
@Mock
private ApiKeyPairVO apiKeyPairVOMock;
@Mock
private Pair<List<ApiKeyPairVO>, Integer> pairMock;
private final long accountMockId = 100L;
@Mock
@ -128,9 +156,19 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
@Mock
ConfigKey<Boolean> allowOperationsOnUsersInSameAccountMock;
@Mock
RoleService roleService;
@Mock
ApiKeyPairPermissionsDao apiKeyPairPermissionsDaoMock;
@Mock
ApiKeyPairDao apiKeyPairDaoMock;
@Mock
RegisterUserKeysCmd registerCmdMock;
@Before
public void setUp() throws Exception {
enableUserTwoFactorAuthenticationMock = Mockito.mock(ConfigKey.class);
@ -150,6 +188,13 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Mockito.doReturn(userVoIdMock).when(userVoMock).getId();
Mockito.lenient().doNothing().when(accountManagerImpl).checkRoleEscalation(accountMock, accountMock);
Mockito.doReturn(accountMockId).when(accountVoMock).getId();
Mockito.when(apiKeyPairDaoMock.persist(Mockito.any())).thenAnswer(i -> {
ApiKeyPairVO keyPair = (ApiKeyPairVO) i.getArguments()[0];
keyPair.setId(1L);
return keyPair;
});
}
@Test
@ -296,6 +341,7 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Mockito.doNothing().when(accountManagerImpl).checkAccountAndAccess(Mockito.any(), Mockito.any());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(userVoMock);
Mockito.doNothing().when(accountManagerImpl).removeUserApiKeys(Mockito.anyLong());
accountManagerImpl.deleteUser(cmd);
}
}
@ -334,13 +380,10 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
}
@Test(expected = PermissionDeniedException.class)
public void testgetUserCmd() {
public void testGetUserCmd() {
CallContext.register(callingUser, callingAccount); // Calling account is user account i.e normal account
Mockito.when(_listkeyscmd.getID()).thenReturn(1L);
Mockito.when(_getkeyscmd.getId()).thenReturn(1L);
Mockito.when(accountManagerImpl.getActiveUser(1L)).thenReturn(userVoMock);
Mockito.when(userAccountDao.findById(1L)).thenReturn(userAccountVO);
Mockito.when(userAccountVO.getAccountId()).thenReturn(1L);
Mockito.lenient().when(accountManagerImpl.getAccount(Mockito.anyLong())).thenReturn(accountMock); // Queried account - admin account
Mockito.lenient().when(callingUser.getAccountId()).thenReturn(1L);
Mockito.lenient().when(_accountDao.findById(1L)).thenReturn(callingAccount);
@ -348,17 +391,15 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Mockito.lenient().when(accountService.isNormalUser(Mockito.anyLong())).thenReturn(Boolean.TRUE);
Mockito.lenient().when(accountMock.getAccountId()).thenReturn(2L);
accountManagerImpl.getKeys(_listkeyscmd);
accountManagerImpl.getKeys(_getkeyscmd);
}
@Test(expected = PermissionDeniedException.class)
public void testGetUserKeysCmdDomainAdminRootAdminUser() {
CallContext.register(callingUser, callingAccount);
Mockito.when(_listkeyscmd.getID()).thenReturn(2L);
Mockito.when(_getkeyscmd.getId()).thenReturn(2L);
Mockito.when(accountManagerImpl.getActiveUser(2L)).thenReturn(userVoMock);
Mockito.when(userAccountDao.findById(2L)).thenReturn(userAccountVO);
Mockito.when(userAccountVO.getAccountId()).thenReturn(2L);
Mockito.when(userDetailsDaoMock.listDetailsKeyPairs(Mockito.anyLong())).thenReturn(null);
Mockito.when(userVoMock.getAccountId()).thenReturn(2L);
// Queried account - admin account
AccountVO adminAccountMock = Mockito.mock(AccountVO.class);
@ -377,7 +418,7 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Mockito.lenient().when(accountService.isDomainAdmin(Mockito.anyLong())).thenReturn(Boolean.TRUE);
Mockito.lenient().when(accountMock.getAccountId()).thenReturn(2L);
accountManagerImpl.getKeys(_listkeyscmd);
accountManagerImpl.getKeys(_getkeyscmd);
}
@Test
@ -472,7 +513,6 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Mockito.doReturn("password").when(UpdateUserCmdMock).getPassword();
Mockito.doReturn("newpassword").when(UpdateUserCmdMock).getCurrentPassword();
Mockito.doReturn(userVoMock).when(accountManagerImpl).retrieveAndValidateUser(UpdateUserCmdMock);
Mockito.doNothing().when(accountManagerImpl).validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
Mockito.doReturn(accountMock).when(accountManagerImpl).retrieveAndValidateAccount(userVoMock);
Mockito.doNothing().when(accountManagerImpl).validateAndUpdateFirstNameIfNeeded(UpdateUserCmdMock, userVoMock);
@ -523,63 +563,73 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
}
@Test
public void validateAndUpdatApiAndSecretKeyIfNeededTestNoKeys() {
public void validateAndUpdateApiAndSecretKeyIfNeededTestNoKeys() {
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
Mockito.verify(userAccountDao, Mockito.times(0)).getUserByApiKey(Mockito.anyString());
Mockito.verify(apiKeyPairDaoMock, Mockito.times(0)).getLastApiKeyCreatedByUser(Mockito.anyLong());
Mockito.verify(accountManagerImpl, Mockito.times(0)).findUserByApiKey(Mockito.anyString());
}
@Test(expected = InvalidParameterValueException.class)
public void validateAndUpdatApiAndSecretKeyIfNeededTestOnlyApiKeyInformed() {
public void validateAndUpdateApiAndSecretKeyIfNeededTestOnlyApiKeyInformed() {
Mockito.doReturn("apiKey").when(UpdateUserCmdMock).getApiKey();
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
}
@Test(expected = InvalidParameterValueException.class)
public void validateAndUpdatApiAndSecretKeyIfNeededTestOnlySecretKeyInformed() {
public void validateAndUpdateApiAndSecretKeyIfNeededTestOnlySecretKeyInformed() {
Mockito.doReturn("secretKey").when(UpdateUserCmdMock).getSecretKey();
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
}
@Test(expected = InvalidParameterValueException.class)
public void validateAndUpdatApiAndSecretKeyIfNeededTestApiKeyAlreadyUsedBySomeoneElse() {
public void validateAndUpdateApiAndSecretKeyIfNeededTestApiKeyAlreadyUsedBySomeoneElse() {
String apiKey = "apiKey";
Mockito.doReturn(apiKey).when(UpdateUserCmdMock).getApiKey();
Mockito.doReturn("secretKey").when(UpdateUserCmdMock).getSecretKey();
Mockito.doReturn(1L).when(userVoMock).getId();
Mockito.doReturn(apiKeyPairVOMock).when(apiKeyPairDaoMock).getLastApiKeyCreatedByUser(1L);
User otherUserMock = Mockito.mock(User.class);
UserAccount UserAccountMock = Mockito.mock(UserAccount.class);
Mockito.doReturn(UserAccountMock).when(userAccountDao).getUserByApiKey(apiKey);
Ternary<User, Account, ApiKeyPair> pairUserAccountMock = new Ternary<User, Account, ApiKeyPair>(otherUserMock, Mockito.mock(Account.class), apiKeyPairVOMock);
Mockito.doReturn(pairUserAccountMock).when(accountManagerImpl).findUserByApiKey(apiKey);
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
}
@Test(expected = InvalidParameterValueException.class)
public void validateAndUpdateApiAndSecretKeyIfNeededTestUserHasNoActiveApiKeyPair() {
String apiKey = "apiKey";
Mockito.doReturn(apiKey).when(UpdateUserCmdMock).getApiKey();
Mockito.doReturn("secretKey").when(UpdateUserCmdMock).getSecretKey();
Mockito.doReturn(1L).when(userVoMock).getId();
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
}
@Test
public void validateAndUpdatApiAndSecretKeyIfNeededTest() {
public void validateAndUpdateApiAndSecretKeyIfNeededTest() {
String apiKey = "apiKey";
Mockito.doReturn(apiKey).when(UpdateUserCmdMock).getApiKey();
String secretKey = "secretKey";
Mockito.doReturn(secretKey).when(UpdateUserCmdMock).getSecretKey();
User otherUserMock = Mockito.mock(User.class);
Mockito.doReturn(null).when(userAccountDao).getUserByApiKey(apiKey);
Mockito.doReturn(1L).when(userVoMock).getId();
Mockito.doReturn(apiKeyPairVOMock).when(apiKeyPairDaoMock).getLastApiKeyCreatedByUser(1L);
accountManagerImpl.validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
Mockito.verify(userAccountDao).getUserByApiKey(apiKey);
Mockito.verify(userVoMock).setApiKey(apiKey);
Mockito.verify(userVoMock).setSecretKey(secretKey);
Mockito.verify(accountManagerImpl).findUserByApiKey(apiKey);
Mockito.verify(apiKeyPairVOMock).setApiKey(apiKey);
Mockito.verify(apiKeyPairVOMock).setSecretKey(secretKey);
}
@Test
public void validateAndUpdatUserApiKeyAccess() {
public void validateAndUpdateUserApiKeyAccess() {
Mockito.doReturn("Enabled").when(UpdateUserCmdMock).getApiKeyAccess();
try (MockedStatic<ActionEventUtils> eventUtils = Mockito.mockStatic(ActionEventUtils.class)) {
Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
@ -599,7 +649,7 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
}
@Test
public void validateAndUpdatAccountApiKeyAccess() {
public void validateAndUpdateAccountApiKeyAccess() {
Mockito.doReturn("Inherit").when(UpdateAccountCmdMock).getApiKeyAccess();
try (MockedStatic<ActionEventUtils> eventUtils = Mockito.mockStatic(ActionEventUtils.class)) {
Mockito.when(ActionEventUtils.onActionEvent(Mockito.anyLong(), Mockito.anyLong(),
@ -613,7 +663,7 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
}
@Test(expected = InvalidParameterValueException.class)
public void validateAndUpdatAccountApiKeyAccessInvalidParameter() {
public void validateAndUpdateAccountApiKeyAccessInvalidParameter() {
Mockito.doReturn("False").when(UpdateAccountCmdMock).getApiKeyAccess();
accountManagerImpl.validateAndUpdateAccountApiKeyAccess(UpdateAccountCmdMock, accountVoMock);
}
@ -1271,6 +1321,354 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
Assert.assertEquals(userAccountVOList.get(0), userAccounts.get(0));
}
@Test
public void createApiKeyAndSecretKeyTestWithEmptyRules() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = 111L;
Mockito.when(registerCmdMock.getRules()).thenReturn(List.of());
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
Mockito.when(userDaoMock.findById(any())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairDaoMock.findBySecretKey(Mockito.anyString())).thenReturn(null);
Mockito.when(roleService.findAllRolePermissionsEntityBy(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(
new RolePermissionVO(1L, "api2", RolePermissionEntity.Permission.ALLOW, "description")
));
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
Mockito.when(roleService.roleHasPermission(Mockito.anyMap(), Mockito.anyList())).thenReturn(true);
accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(0)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(0)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test(expected = InvalidParameterValueException.class)
public void createApiKeyAndSecretKeyTestPermissionNotPresentOnAccount() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api1",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(any())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(roleService.findAllRolePermissionsEntityBy(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(
new RolePermissionVO(1L, "api2", RolePermissionEntity.Permission.ALLOW, "description")
));
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(1)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(0)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test(expected = InvalidParameterValueException.class)
public void createApiKeyAndSecretTestKeyDeniedOnAccount() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairDaoMock.findBySecretKey(Mockito.anyString())).thenReturn(null);
Mockito.when(roleService.findAllRolePermissionsEntityBy(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(
new RolePermissionVO(1L, "api", RolePermissionEntity.Permission.DENY, "description")
));
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(1)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(0)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test
public void createApiKeyAndSecretKeyTestAllowedOnAccount() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = callingUser.getId();
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
ApiKeyPairPermissionVO permissionVO = new ApiKeyPairPermissionVO();
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairPermissionsDaoMock.persist(Mockito.any(ApiKeyPairPermissionVO.class))).thenReturn(permissionVO);
Mockito.doReturn(true).when(roleService).roleHasPermission(Mockito.any(), Mockito.any());
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
Assert.assertEquals((long) accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock).getUserId(), userId);
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(1)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test
public void createApiAndSecretKeyTestWithNonEmptyDates() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
Date startDate = Date.from(Instant.parse("2024-03-03T10:15:30.00Z"));
Date endDate = Date.from(Instant.parse("2124-03-04T10:15:30.00Z"));
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
Mockito.when(registerCmdMock.getStartDate()).thenReturn(startDate);
Mockito.when(registerCmdMock.getEndDate()).thenReturn(endDate);
Mockito.when(registerCmdMock.getDescription()).thenReturn("key description");
UserVO mockUser = new UserVO(userId);
ApiKeyPairPermissionVO permissionVO = new ApiKeyPairPermissionVO();
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairPermissionsDaoMock.persist(Mockito.any(ApiKeyPairPermissionVO.class))).thenReturn(permissionVO);
Mockito.when(apiKeyPairDaoMock.findBySecretKey(Mockito.anyString())).thenReturn(null);
Mockito.doReturn(true).when(roleService).roleHasPermission(Mockito.any(), Mockito.any());
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
ApiKeyPair response = accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(1)).persist(Mockito.any(ApiKeyPairVO.class));
Assert.assertEquals(userId, (long) response.getUserId());
Assert.assertEquals(response.getStartDate(), startDate);
Assert.assertEquals(response.getEndDate(), endDate);
Assert.assertEquals("key description", response.getDescription());
}
@Test(expected = InvalidParameterValueException.class)
public void createApiAndSecretKeyTestWithExpiredDate() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
Date startDate = Date.from(Instant.parse("2024-03-01T10:15:30.00Z"));
Date endDate = Date.from(Instant.parse("2024-03-02T10:15:30.00Z"));
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
Mockito.when(registerCmdMock.getStartDate()).thenReturn(startDate);
Mockito.when(registerCmdMock.getEndDate()).thenReturn(endDate);
Mockito.when(registerCmdMock.getDescription()).thenReturn("key description");
UserVO mockUser = new UserVO(userId);
ApiKeyPairPermissionVO permissionVO = new ApiKeyPairPermissionVO();
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
ApiKeyPair response = accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Assert.assertEquals((long) response.getUserId(), userId);
Assert.assertEquals(response.getStartDate(), startDate);
Assert.assertEquals(response.getEndDate(), endDate);
Assert.assertEquals(response.getDescription(), "key description");
}
@Test(expected = InvalidParameterValueException.class)
public void createApiAndSecretKeyTestWithInvalidDate() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
Date endDate = Date.from(Instant.parse("2024-03-02T10:15:30.00Z")); // this test will break in 100 years :O
Date startDate = Date.from(Instant.parse("2024-10-02T10:15:30.00Z"));
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
Mockito.when(registerCmdMock.getStartDate()).thenReturn(startDate);
Mockito.when(registerCmdMock.getEndDate()).thenReturn(endDate);
Mockito.when(registerCmdMock.getDescription()).thenReturn("key description");
UserVO mockUser = new UserVO(userId);
ApiKeyPairPermissionVO permissionVO = new ApiKeyPairPermissionVO();
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
ApiKeyPair response = accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Assert.assertEquals(userId, (long) response.getUserId());
Assert.assertEquals(response.getStartDate(), startDate);
Assert.assertEquals(response.getEndDate(), endDate);
Assert.assertEquals("key description", response.getDescription());
}
@Test(expected = InvalidParameterValueException.class)
public void createApiAndSecretKeyTestWithMultipleAllowedPermissionsOneDenied() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api1",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
rules.add(Map.of(
ApiConstants.RULE, "api2",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
rules.add(Map.of(
ApiConstants.RULE, "api3",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
UserVO mockUser = new UserVO(userId);
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairDaoMock.findBySecretKey(Mockito.anyString())).thenReturn(null);
Mockito.when(roleService.findAllRolePermissionsEntityBy(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(
new RolePermissionVO(1L, "api1", RolePermissionEntity.Permission.ALLOW, "description-1"),
new RolePermissionVO(1L, "api2", RolePermissionEntity.Permission.ALLOW, "description-2"),
new RolePermissionVO(1L, "api3", RolePermissionEntity.Permission.DENY, "description-3")
));
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(1)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(0)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test(expected = InvalidParameterValueException.class)
public void createApiAndSecretKeyTestWithMultipleAllowedPermissionsOneDoesNotExist() {
CallContext.register(callingUser, callingAccount);
Mockito.lenient().when(callingUser.getId()).thenReturn(111L);
long userId = 111L;
List<Map<String, Object>> rules = new ArrayList<>();
rules.add(Map.of(
ApiConstants.RULE, "api1",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
rules.add(Map.of(
ApiConstants.RULE, "api2",
ApiConstants.PERMISSION, RolePermission.Permission.ALLOW,
ApiConstants.DESCRIPTION, "description"
));
rules.add(Map.of(
ApiConstants.RULE, "api3",
ApiConstants.PERMISSION, RolePermission.Permission.DENY,
ApiConstants.DESCRIPTION, "description"
));
Mockito.when(registerCmdMock.getRules()).thenReturn(rules);
Mockito.when(registerCmdMock.getUserId()).thenReturn(userId);
ApiKeyPairVO apiKeyPairVO = new ApiKeyPairVO();
apiKeyPairVO.setUserId(userId);
apiKeyPairVO.setId(1L);
Mockito.when(userDaoMock.findById(Mockito.anyLong())).thenReturn(userVoMock);
Mockito.when(_accountDao.findById(Mockito.anyLong())).thenReturn(accountVoMock);
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(Account.class), Mockito.isNull(), Mockito.anyBoolean(), Mockito.any(Account.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(Mockito.any(User.class));
Mockito.when(apiKeyPairDaoMock.findBySecretKey(Mockito.anyString())).thenReturn(null);
Mockito.when(roleService.findAllRolePermissionsEntityBy(Mockito.anyLong(), Mockito.anyBoolean())).thenReturn(List.of(
new RolePermissionVO(1L, "api1", RolePermissionEntity.Permission.ALLOW, "description-1"),
new RolePermissionVO(1L, "api2", RolePermissionEntity.Permission.ALLOW, "description-2")
));
Mockito.when(roleService.findRole(Mockito.anyLong())).thenReturn(new RoleVO());
accountManagerImpl.createApiKeyAndSecretKey(registerCmdMock);
Mockito.verify(apiKeyPairDaoMock, Mockito.times(0)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(0)).persist(Mockito.any(ApiKeyPairPermissionVO.class));
}
@Test
public void validateAccountHasAccessToResourceTestValidatesAccessToControlledEntity() {
VMInstanceVO vmInstanceVo = new VMInstanceVO();
Mockito.doNothing().when(accountManagerImpl).checkAccess(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any());
accountManagerImpl.validateAccountHasAccessToResource(callingAccount, AccessType.UseEntry, vmInstanceVo);
Mockito.verify(accountManagerImpl).checkAccess(callingAccount, AccessType.UseEntry, true, vmInstanceVo);
}
@Test
public void testDeleteWebhooksForAccount() {
try (MockedStatic<ComponentContext> mockedComponentContext = Mockito.mockStatic(ComponentContext.class)) {
@ -1554,6 +1952,15 @@ public class AccountManagerImplTest extends AccountManagentImplTestBase {
accountManagerImpl.assertUserNotAlreadyInDomain(existingUser, originalAccount);
}
@Test
public void deleteApiKeyTestOnePermission() {
Mockito.when(apiKeyPairPermissionsDaoMock.findAllByApiKeyPairId(Mockito.any())).thenReturn(List.of(new ApiKeyPairPermissionVO()));
Mockito.when(userDaoMock.findByIdIncludingRemoved(Mockito.any())).thenReturn(new UserVO());
accountManagerImpl.deleteApiKey(new ApiKeyPairVO(1L, 1L));
Mockito.verify(apiKeyPairPermissionsDaoMock, Mockito.times(1)).remove(Mockito.anyLong());
Mockito.verify(apiKeyPairDaoMock, Mockito.times(1)).remove(Mockito.anyLong());
}
@Test
public void testCheckCallerRoleTypeAllowedToUpdateUserSameAccount() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(accountMock);

View File

@ -89,6 +89,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.storage.template.VnfTemplateManager;
@ -1823,35 +1824,35 @@ public class UserVmManagerImplTest {
Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong());
Mockito.doReturn(false).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong());
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock));
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock, null));
}
@Test
public void checkExpungeVmPermissionTestAccountIsNotAdminConfigTrueNoApiAccessThrowsPermissionDeniedException () {
Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong());
Mockito.doReturn(true).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong());
doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine");
doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine", null);
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock));
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock, null));
}
@Test
public void checkExpungeVmPermissionTestAccountIsNotAdminConfigTrueHasApiAccessReturnNothing () {
Mockito.doReturn(false).when(accountManager).isAdmin(Mockito.anyLong());
Mockito.doReturn(true).when(userVmManagerImpl).getConfigAllowUserExpungeRecoverVm(Mockito.anyLong());
userVmManagerImpl.checkExpungeVmPermission(accountMock);
userVmManagerImpl.checkExpungeVmPermission(accountMock, null);
}
@Test
public void checkExpungeVmPermissionTestAccountIsAdminNoApiAccessThrowsPermissionDeniedException () {
Mockito.doReturn(true).when(accountManager).isAdmin(Mockito.anyLong());
doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine");
doThrow(PermissionDeniedException.class).when(accountManager).checkApiAccess(accountMock, "expungeVirtualMachine", null);
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock));
Assert.assertThrows(PermissionDeniedException.class, () -> userVmManagerImpl.checkExpungeVmPermission(accountMock, null));
}
@Test
public void checkExpungeVmPermissionTestAccountIsAdminHasApiAccessReturnNothing () {
Mockito.doReturn(true).when(accountManager).isAdmin(Mockito.anyLong());
userVmManagerImpl.checkExpungeVmPermission(accountMock);
userVmManagerImpl.checkExpungeVmPermission(accountMock, null);
}
@Test
@ -3661,7 +3662,7 @@ public class UserVmManagerImplTest {
when(callingAccount.getId()).thenReturn(accountId);
when(callContext.getCallingAccount()).thenReturn(callingAccount);
when(accountManager.isAdmin(callingAccount.getId())).thenReturn(true);
doNothing().when(accountManager).checkApiAccess(callingAccount, BaseCmd.getCommandNameByClass(ExpungeVMCmd.class));
doNothing().when(accountManager).checkApiAccess(callingAccount, BaseCmd.getCommandNameByClass(ExpungeVMCmd.class), null);
try (MockedStatic<CallContext> mockedCallContext = mockStatic(CallContext.class)) {
mockedCallContext.when(CallContext::current).thenReturn(callContext);
mockedCallContext.when(() -> CallContext.register(callContext, ApiCommandResourceType.Volume)).thenReturn(callContext);
@ -3671,6 +3672,9 @@ public class UserVmManagerImplTest {
when(cmd.getExpunge()).thenReturn(expunge);
List<Long> volumeIds = List.of(volumeId);
when(cmd.getVolumeIds()).thenReturn(volumeIds);
AsyncJobVO asyncJobMock = mock(AsyncJobVO.class);
when(cmd.getJob()).thenReturn(asyncJobMock);
when(asyncJobMock.getCmdInfo()).thenReturn("{}");
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(vmId);

View File

@ -21,7 +21,6 @@ import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.RolePermissionEntity.Permission;
import org.apache.cloudstack.acl.dao.RoleDao;
import org.apache.cloudstack.acl.dao.RolePermissionsDao;
import org.apache.commons.collections.CollectionUtils;
@ -58,6 +57,10 @@ public class RoleManagerImplTest {
@Mock
private RolePermission rolePermission2Mock;
@Mock
private RolePermission rolePermission2MockWithDeniedPermission;
@Mock
private RolePermission rolePermission3Mock;
@Mock
private Account callerAccountMock;
@Mock
private Role callerAccountRoleMock;
@ -76,7 +79,7 @@ public class RoleManagerImplTest {
private RoleVO roleVoMock;
private long roleMockId = 1l;
private Map<String, Permission> rolePermissions = new HashMap<>();
private Map<String, RolePermissionEntity> rolePermissions = new HashMap<>();
public void setUpRoleVisibilityTests() {
Mockito.doReturn(List.of("api1", "api2", "api3")).when(accountManagerMock).getApiNameList();
@ -89,18 +92,11 @@ public class RoleManagerImplTest {
Mockito.when(rolePermission2Mock.getRule()).thenReturn(new Rule("api2"));
Mockito.doReturn(RolePermissionEntity.Permission.ALLOW).when(rolePermission1Mock).getPermission();
Mockito.doReturn(RolePermissionEntity.Permission.ALLOW).when(rolePermission2Mock).getPermission();
Mockito.doReturn(RolePermissionEntity.Permission.DENY).when(rolePermission2MockWithDeniedPermission).getPermission();
List<RolePermission> lessPermissionsRolePermissions = Collections.singletonList(rolePermission1Mock);
Mockito.doReturn(1L).when(lessPermissionsRoleMock).getId();
Mockito.when(roleManagerImpl.findAllPermissionsBy(1L)).thenReturn(lessPermissionsRolePermissions);
List<RolePermission> morePermissionsRolePermissions = List.of(rolePermission1Mock, rolePermission2Mock);
Mockito.doReturn(2L).when(morePermissionsRoleMock).getId();
Mockito.when(roleManagerImpl.findAllPermissionsBy(morePermissionsRoleMock.getId())).thenReturn(morePermissionsRolePermissions);
List<RolePermission> differentPermissionsRolePermissions = Collections.singletonList(rolePermission2Mock);
Mockito.doReturn(3L).when(differentPermissionsRoleMock).getId();
Mockito.when(roleManagerImpl.findAllPermissionsBy(differentPermissionsRoleMock.getId())).thenReturn(differentPermissionsRolePermissions);
}
@Before
@ -225,9 +221,6 @@ public class RoleManagerImplTest {
setUpRoleVisibilityTests();
List<Role> roles = new ArrayList<>();
List<RolePermission> callerAccountRolePermissions = List.of(rolePermission1Mock, rolePermission2Mock);
Mockito.when(roleManagerImpl.findAllPermissionsBy(callerAccountRoleMock.getId())).thenReturn(callerAccountRolePermissions);
roles.add(callerAccountRoleMock);
roles.add(lessPermissionsRoleMock);
@ -243,9 +236,11 @@ public class RoleManagerImplTest {
setUpRoleVisibilityTests();
List<Role> roles = new ArrayList<>();
List<RolePermission> callerAccountRolePermissions = Collections.singletonList(rolePermission1Mock);
Mockito.when(roleManagerImpl.findAllPermissionsBy(callerAccountRoleMock.getId())).thenReturn(callerAccountRolePermissions);
List<RolePermissionEntity> callerAccountRolePermissions = Collections.singletonList(rolePermission1Mock);
Mockito.when(roleManagerImpl.findAllRolePermissionsEntityBy(callerAccountRoleMock.getId(), false)).thenReturn(callerAccountRolePermissions);
List<RolePermissionEntity> callerAccountRolePermissions2 = Collections.singletonList(rolePermission2Mock);
Mockito.when(roleManagerImpl.findAllRolePermissionsEntityBy(morePermissionsRoleMock.getId(), false)).thenReturn(callerAccountRolePermissions2);
roles.add(callerAccountRoleMock);
roles.add(morePermissionsRoleMock);
@ -261,8 +256,11 @@ public class RoleManagerImplTest {
setUpRoleVisibilityTests();
List<Role> roles = new ArrayList<>();
List<RolePermission> callerAccountRolePermissions = Collections.singletonList(rolePermission1Mock);
Mockito.when(roleManagerImpl.findAllPermissionsBy(callerAccountRoleMock.getId())).thenReturn(callerAccountRolePermissions);
List<RolePermissionEntity> callerAccountRolePermissions = Collections.singletonList(rolePermission1Mock);
Mockito.when(roleManagerImpl.findAllRolePermissionsEntityBy(callerAccountRoleMock.getId(), false)).thenReturn(callerAccountRolePermissions);
List<RolePermissionEntity> callerAccountRolePermissions3 = Collections.singletonList(rolePermission2Mock);
Mockito.when(roleManagerImpl.findAllRolePermissionsEntityBy(differentPermissionsRoleMock.getId(), false)).thenReturn(callerAccountRolePermissions3);
roles.add(callerAccountRoleMock);
roles.add(differentPermissionsRoleMock);
@ -276,10 +274,10 @@ public class RoleManagerImplTest {
@Test
public void roleHasPermissionTestRoleWithMoreAndSamePermissionsReturnsTrue() {
setUpRoleVisibilityTests();
rolePermissions.put("api1", Permission.ALLOW);
rolePermissions.put("api2", Permission.ALLOW);
rolePermissions.put("api1", rolePermission1Mock);
rolePermissions.put("api2", rolePermission2Mock);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, lessPermissionsRoleMock);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, Collections.singletonList(rolePermission1Mock));
Assert.assertTrue(result);
}
@ -287,10 +285,10 @@ public class RoleManagerImplTest {
@Test
public void roleHasPermissionTestRoleAllowedApisDoesNotContainRoleToAccessAllowedApiReturnsFalse() {
setUpRoleVisibilityTests();
rolePermissions.put("api2", Permission.ALLOW);
rolePermissions.put("api3", Permission.ALLOW);
rolePermissions.put("api2", rolePermission2Mock);
rolePermissions.put("api3", rolePermission3Mock);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, morePermissionsRoleMock);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, List.of(rolePermission1Mock, rolePermission2Mock));
Assert.assertFalse(result);
}
@ -298,10 +296,10 @@ public class RoleManagerImplTest {
@Test
public void roleHasPermissionTestRolePermissionsDeniedApiContainRoleToAccessAllowedApiReturnsFalse() {
setUpRoleVisibilityTests();
rolePermissions.put("api1", Permission.ALLOW);
rolePermissions.put("api2", Permission.DENY);
rolePermissions.put("api1", rolePermission1Mock);
rolePermissions.put("api2", rolePermission2MockWithDeniedPermission);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, morePermissionsRoleMock);
boolean result = roleManagerImpl.roleHasPermission(rolePermissions, List.of(rolePermission1Mock, rolePermission2Mock));
Assert.assertFalse(result);
}
@ -310,11 +308,11 @@ public class RoleManagerImplTest {
public void getRolePermissionsTestRoleReturnsRolePermissions() {
setUpRoleVisibilityTests();
Map<String, Permission> roleRulesAndPermissions = roleManagerImpl.getRoleRulesAndPermissions(morePermissionsRoleMock.getId());
Map<String, RolePermissionEntity> roleRulesAndPermissions = roleManagerImpl.getRoleRulesAndPermissions(List.of(rolePermission1Mock, rolePermission2Mock));
Assert.assertEquals(2, roleRulesAndPermissions.size());
Assert.assertEquals(roleRulesAndPermissions.get("api1"), Permission.ALLOW);
Assert.assertEquals(roleRulesAndPermissions.get("api2"), Permission.ALLOW);
Assert.assertEquals(roleRulesAndPermissions.get("api1"), rolePermission1Mock);
Assert.assertEquals(roleRulesAndPermissions.get("api2"), rolePermission2Mock);
}
@Test

View File

@ -1744,7 +1744,7 @@ class TestUserAPIKeys(cloudstackTestCase):
self.apiclient,
id=self.account_2.id
)[0].user
with self.assertRaises(CloudstackAPIException) as e:
with self.assertRaises(Exception) as e:
User.registerUserKeys(cs_api, users[0].id)

View File

@ -148,32 +148,24 @@ class CSTestClient(object):
"Client Creation Failed")
return FAILED
getuser_keys = getUserKeys.getUserKeysCmd()
getuser_keys.id = list_user_res[0].id
getuser_keys_res = self.__apiClient.getUserKeys(getuser_keys)
if getuser_keys_res is None :
self.__logger.error("__createApiClient: API "
"Client Creation Failed")
return FAILED
api_key = getuser_keys_res.apikey
security_key = getuser_keys_res.secretkey
user_id = list_user_res[0].id
if api_key is None:
getuser_keys = getUserKeys.getUserKeysCmd()
getuser_keys.id = user_id
getuser_keys_res = self.__apiClient.getUserKeys(getuser_keys)
if getuser_keys_res['apikey'] is None:
ret = self.__getKeys(user_id)
if ret != FAILED:
mgmt_details.apiKey = ret[0]
mgmt_details.securityKey = ret[1]
else:
if ret == FAILED:
self.__logger.error("__createApiClient: API Client "
"Creation Failed while "
"Registering User")
return FAILED
mgmt_details.apiKey = ret[0]
mgmt_details.securityKey = ret[1]
else:
mgmt_details.port = 8080
mgmt_details.apiKey = api_key
mgmt_details.securityKey = security_key
mgmt_details.apiKey = getuser_keys_res['apikey']
mgmt_details.securityKey = getuser_keys_res['secretkey']
'''
Now Create the Connection objects and Api Client using
new details
@ -216,6 +208,7 @@ class CSTestClient(object):
try:
register_user = registerUserKeys.registerUserKeysCmd()
register_user.id = userid
register_user.name = f"keypair-{userid}"
register_user_res = \
self.__apiClient.registerUserKeys(register_user)
if not register_user_res:
@ -224,13 +217,13 @@ class CSTestClient(object):
getuser_keys = getUserKeys.getUserKeysCmd()
getuser_keys.id = userid
getuser_keys_res = self.__apiClient.getUserKeys(getuser_keys)
if getuser_keys_res is None :
if getuser_keys_res is None:
self.__logger.error("__createApiClient: API "
"Client Creation Failed")
"Client Creation Failed")
return FAILED
api_key = getuser_keys_res.apikey
security_key = getuser_keys_res.secretkey
api_key = getuser_keys_res['apikey']
security_key = getuser_keys_res['secretkey']
return (api_key, security_key)
except Exception as e:
self.__logger.exception("Exception Occurred Under __geKeys : "

View File

@ -335,6 +335,7 @@ class User:
def registerUserKeys(cls, apiclient, userid):
cmd = registerUserKeys.registerUserKeysCmd()
cmd.id = userid
cmd.name = f"keypair-{userid}"
return apiclient.registerUserKeys(cmd)
def update(self, apiclient, **kwargs):