CLOUDSTACK-9099: Added a separate API to apikey and secretkey

This commit is contained in:
Jayapal 2017-05-17 14:15:20 +05:30
parent d7c5994f03
commit 87cf33ac5c
12 changed files with 330 additions and 66 deletions

View File

@ -21,6 +21,7 @@ import java.util.Map;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
import com.cloud.domain.Domain;
@ -28,6 +29,7 @@ import com.cloud.exception.PermissionDeniedException;
import com.cloud.offering.DiskOffering;
import com.cloud.offering.ServiceOffering;
public interface AccountService {
/**
@ -124,6 +126,8 @@ public interface AccountService {
void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException;
void checkAccess(User user, ControlledEntity entity);
void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName,
ControlledEntity... entities) throws PermissionDeniedException;
@ -136,4 +140,5 @@ public interface AccountService {
*/
UserAccount getUserAccountById(Long userId);
public Map<String, String> getKeys(GetUserKeysCmd cmd);
}

View File

@ -0,0 +1,77 @@
// 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.User;
import org.apache.cloudstack.acl.RoleType;
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.RegisterResponse;
import org.apache.cloudstack.api.response.UserResponse;
import java.util.Map;
import java.util.logging.Logger;
@APICommand(name = GetUserKeysCmd.APINAME,
description = "This command allows the user to query the seceret and API keys for the account",
responseObject = RegisterResponse.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 static final Logger s_logger = Logger.getLogger(RegisterCmd.class.getName());
public static final String APINAME = "getUserKeys";
public Long getID(){
return id;
}
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
public long getEntityOwnerId(){
User user = _entityMgr.findById(User.class, getID());
if(user != null){
return user.getAccountId();
}
else return Account.ACCOUNT_ID_SYSTEM;
}
public void execute(){
Map<String, String> keys = _accountService.getKeys(this);
RegisterResponse response = new RegisterResponse();
if(keys != null){
response.setApiKey(keys.get("apikey"));
response.setSecretKey(keys.get("secretkey"));
}
response.setObjectName("userkeys");
response.setResponseName(getCommandName());
this.setResponseObject(response);
}
}

View File

@ -94,6 +94,7 @@ public class UserResponse extends BaseResponse {
@Param(description = "the api key of the user", isSensitive = true)
private String apiKey;
@Deprecated
@SerializedName("secretkey")
@Param(description = "the secret key of the user", isSensitive = true)
private String secretKey;
@ -236,7 +237,6 @@ public class UserResponse extends BaseResponse {
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}

View File

@ -24,6 +24,8 @@ import java.net.InetAddress;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.ControlledEntity;
@ -432,4 +434,24 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
public void checkAccess(Account account, DiskOffering dof) throws PermissionDeniedException {
// TODO Auto-generated method stub
}
@Override
public Map<String, String> getKeys(GetUserKeysCmd cmd){
return null;
}
@Override
public void checkAccess(User user, ControlledEntity entity)
throws PermissionDeniedException {
}
@Override
public String getConfigComponentName() {
return null;
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return null;
}
}

View File

@ -311,6 +311,7 @@ import com.cloud.vm.dao.UserVmDetailsDao;
import com.cloud.vm.dao.VMInstanceDao;
import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import com.cloud.user.AccountManager;
public class ApiDBUtils {
private static ManagementServer s_ms;
@ -1707,6 +1708,9 @@ public class ApiDBUtils {
public static UserResponse newUserResponse(UserAccountJoinVO usr, Long domainId) {
UserResponse response = s_userAccountJoinDao.newUserResponse(usr);
if(!AccountManager.UseSecretKeyInResponse.value()){
response.setSecretKey(null);
}
// Populate user account role information
if (usr.getAccountRoleId() != null) {
Role role = s_roleService.findRole( usr.getAccountRoleId());

View File

@ -220,6 +220,7 @@ 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.ListUsersCmd;
import org.apache.cloudstack.api.command.admin.user.LockUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
@ -3067,6 +3068,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(ConfigureOutOfBandManagementCmd.class);
cmdList.add(IssueOutOfBandManagementPowerActionCmd.class);
cmdList.add(ChangeOutOfBandManagementPasswordCmd.class);
cmdList.add(GetUserKeysCmd.class);
return cmdList;
}

View File

@ -33,12 +33,14 @@ import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
/**
* AccountManager includes logic that deals with accounts, domains, and users.
*
*/
public interface AccountManager extends AccountService {
public interface AccountManager extends AccountService, Configurable{
/**
* Disables an account by accountId
* @param accountId
@ -198,4 +200,11 @@ public interface AccountManager extends AccountService {
public static final String MESSAGE_ADD_ACCOUNT_EVENT = "Message.AddAccount.Event";
public static final String MESSAGE_REMOVE_ACCOUNT_EVENT = "Message.RemoveAccount.Event";
public static final ConfigKey<Boolean> UseSecretKeyInResponse = new ConfigKey<Boolean>(
"Advanced",
Boolean.class,
"use.secret.key.in.response",
"false",
"This parameter allows the users to enable or disable of showing secret key as a part of response for various APIs. By default it is set to false.",
true);
}

View File

@ -16,52 +16,6 @@
// under the License.
package com.cloud.user;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.QuerySelector;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
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.region.gslb.GlobalLoadBalancerRuleDao;
import org.apache.cloudstack.utils.baremetal.BaremetalUtils;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.query.vo.ControlledViewEntity;
import com.cloud.configuration.Config;
@ -168,6 +122,53 @@ 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.acl.ControlledEntity;
import org.apache.cloudstack.acl.QuerySelector;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.dao.AffinityGroupDao;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
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.RegisterCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.cloudstack.region.gslb.GlobalLoadBalancerRuleDao;
import org.apache.cloudstack.utils.baremetal.BaremetalUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager {
public static final Logger s_logger = Logger.getLogger(AccountManagerImpl.class);
@ -1363,10 +1364,10 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
boolean success = Transaction.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
boolean success = doSetUserStatus(userId, State.enabled);
boolean success = doSetUserStatus(userId, State.enabled);
// make sure the account is enabled too
success = success && enableAccount(user.getAccountId());
// make sure the account is enabled too
success = success && enableAccount(user.getAccountId());
return success;
}
@ -2220,6 +2221,24 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return _accountDao.findUserAccountByApiKey(apiKey);
}
@Override
public Map<String, String> getKeys(GetUserKeysCmd cmd){
final long userId = cmd.getID();
User user = getActiveUser(userId);
if(user==null){
throw new InvalidParameterValueException("Unable to find user by id");
}
final ControlledEntity account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user.
checkAccess(CallContext.current().getCallingUser(), account);
Map <String, String> keys = new HashMap<String, String>();
keys.put("apikey", user.getApiKey());
keys.put("secretkey", user.getSecretKey());
return keys;
}
@Override
@DB
@ActionEvent(eventType = EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY, eventDescription = "register for the developer API keys")
@ -2632,4 +2651,28 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
assert false : "How can all of the security checkers pass on checking this caller?";
throw new PermissionDeniedException("There's no way to confirm " + account + " has access to " + dof);
}
@Override
public void checkAccess(User user, ControlledEntity entity)
throws PermissionDeniedException {
for(SecurityChecker checker : _securityCheckers){
if(checker.checkAccess(user,entity)){
if(s_logger.isDebugEnabled()){
s_logger.debug("Access granted to " + user + "to " + entity + "by " + checker.getName());
}
return;
}
}
throw new PermissionDeniedException("There's no way to confirm " + user + " has access to " + entity);
}
@Override
public String getConfigComponentName() {
return AccountManager.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[]{UseSecretKeyInResponse};
}
}

View File

@ -20,9 +20,14 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import com.cloud.acl.DomainChecker;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.server.auth.UserAuthenticator;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.context.CallContext;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
@ -170,5 +175,38 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.verify(userAuthenticator, Mockito.never()).authenticate("test", "", 1L, null);
}
@Mock
AccountVO callingAccount;
@Mock
DomainChecker domainChecker;
@Mock
AccountService accountService;
@Mock
private GetUserKeysCmd _listkeyscmd;
@Mock
private Account _account;
@Mock
private User _user;
@Mock
private UserAccountVO userAccountVO;
@Test (expected = PermissionDeniedException.class)
public void testgetUserCmd(){
CallContext.register(callingUser, callingAccount); // Calling account is user account i.e normal account
Mockito.when(_listkeyscmd.getID()).thenReturn(1L);
Mockito.when(accountManager.getActiveUser(1L)).thenReturn(_user);
Mockito.when(accountManager.getUserAccountById(1L)).thenReturn(userAccountVO);
Mockito.when(userAccountVO.getAccountId()).thenReturn(1L);
Mockito.when(accountManager.getAccount(Mockito.anyLong())).thenReturn(_account); // Queried account - admin account
Mockito.when(callingUser.getAccountId()).thenReturn(1L);
Mockito.when(_accountDao.findById(1L)).thenReturn(callingAccount);
Mockito.when(accountService.isNormalUser(Mockito.anyLong())).thenReturn(Boolean.TRUE);
Mockito.when(_account.getAccountId()).thenReturn(2L);
accountManager.getKeys(_listkeyscmd);
}
}

View File

@ -22,6 +22,8 @@ import java.net.InetAddress;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.acl.ControlledEntity;
@ -401,5 +403,24 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco
return null;
}
@Override
public Map<String, String> getKeys(GetUserKeysCmd cmd) {
return null;
}
@Override
public void checkAccess(User user, ControlledEntity entity)
throws PermissionDeniedException {
}
@Override
public String getConfigComponentName() {
return null;
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return null;
}
}

View File

@ -147,9 +147,19 @@ class CSTestClient(object):
self.__logger.error("__createApiClient: API "
"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
api_key = list_user_res[0].apikey
security_key = list_user_res[0].secretkey
if api_key is None:
ret = self.__getKeys(user_id)
if ret != FAILED:
@ -210,7 +220,18 @@ class CSTestClient(object):
self.__apiClient.registerUserKeys(register_user)
if not register_user_res:
return FAILED
return (register_user_res.apikey, register_user_res.secretkey)
getuser_keys = getUserKeys.getUserKeysCmd()
getuser_keys.id = userid
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
return (api_key, security_key)
except Exception as e:
self.__logger.exception("Exception Occurred Under __geKeys : "
"%s" % GetDetailExceptionInfo(e))
@ -349,8 +370,17 @@ class CSTestClient(object):
listuserRes = self.__apiClient.listUsers(listuser)
userId = listuserRes[0].id
apiKey = listuserRes[0].apikey
securityKey = listuserRes[0].secretkey
getuser_keys = getUserKeys.getUserKeysCmd()
getuser_keys.id = listuserRes[0].id
getuser_keys_res = self.__apiClient.getUserKeys(getuser_keys)
if getuser_keys_res is None or\
(validateList(getuser_keys_res) != PASS):
self.__logger.error("__createApiClient: API "
"Client Creation Failed")
apiKey = getuser_keys_res.apikey
securityKey = getuser_keys_res.secretkey
if apiKey is None:
ret = self.__getKeys(userId)

View File

@ -1638,20 +1638,33 @@
}],
dataProvider: function(args) {
if (isAdmin() || isDomainAdmin()) {
$.ajax({
url: createURL('listUsers'),
if (isAdmin() || isDomainAdmin()) {
$.ajax({
url: createURL('listUsers'),
data: {
id: args.context.users[0].id
},
success: function(json) {
args.response.success({
actionFilter: userActionfilter,
data: json.listusersresponse.user[0]
});
}
});
} else { //normal user doesn't have access listUsers API until Bug 14127 is fixed.
var items = json.listusersresponse.user[0];
$.ajax({
url: createURL('getUserKeys'),//change
data: {
id: args.context.users[0].id//change
},
success: function(json) {
$.extend(items, {
secretkey: json.getuserkeysresponse.userkeys.secretkey//change
});
args.response.success({
actionFilter: userActionfilter,
data: items
});
}
});
}
});
}
else { //normal user doesn't have access listUsers API until Bug 14127 is fixed.
args.response.success({
actionFilter: userActionfilter,
data: args.context.users[0]