CLOUDSTACK-10121 moveUser call

* internal service call for moveUser
* expose moveUser as API
* move uuid to external entity
This commit is contained in:
Daan Hoogland 2017-07-24 18:35:54 +02:00 committed by Daan Hoogland
parent 260b523a4b
commit dceecad03a
17 changed files with 2558 additions and 40 deletions

View File

@ -56,6 +56,11 @@
<artifactId>cloud-framework-ca</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${cs.commons-lang3.version}</version>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -197,6 +197,7 @@ public class EventTypes {
public static final String EVENT_USER_CREATE = "USER.CREATE";
public static final String EVENT_USER_DELETE = "USER.DELETE";
public static final String EVENT_USER_DISABLE = "USER.DISABLE";
public static final String EVENT_USER_MOVE = "USER.MOVE";
public static final String EVENT_USER_UPDATE = "USER.UPDATE";
public static final String EVENT_USER_ENABLE = "USER.ENABLE";
public static final String EVENT_USER_LOCK = "USER.LOCK";

View File

@ -0,0 +1,126 @@
// 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 com.google.common.base.Preconditions;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.AccountResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.UserResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.region.RegionService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.log4j.Logger;
import javax.inject.Inject;
@APICommand(name = "moveUser",
description = "Moves a user to another account",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = false,
responseHasSensitiveInfo = false,
since = "4.11",
authorized = {RoleType.Admin})
public class MoveUserCmd extends BaseCmd {
public static final Logger s_logger = Logger.getLogger(UpdateUserCmd.class.getName());
public static final String APINAME = "moveUser";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@Parameter(name = ApiConstants.ID,
type = CommandType.UUID,
entityType = UserResponse.class,
required = true,
description = "id of the user to be deleted")
private Long id;
@Parameter(name = ApiConstants.ACCOUNT,
type = CommandType.STRING,
description = "Creates the user under the specified account. If no account is specified, the username will be used as the account name.")
private String accountName;
@Parameter(name = ApiConstants.ACCOUNT_ID,
type = CommandType.UUID,
entityType = AccountResponse.class,
description = "Creates the user under the specified domain. Has to be accompanied with the account parameter")
private Long accountId;
@Inject
RegionService _regionService;
/////////////////////////////////////////////////////
/////////////////// Accessors ///////////////////////
/////////////////////////////////////////////////////
public Long getId() {
return id;
}
public String getAccountName() {
return accountName;
}
public Long getAccountId() {
return accountId;
}
/////////////////////////////////////////////////////
/////////////// API Implementation///////////////////
/////////////////////////////////////////////////////
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@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 void execute() {
Preconditions.checkNotNull(getId(),"I have to have an user to move!");
Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!");
CallContext.current().setEventDetails("UserId: " + getId());
boolean result =
_regionService.moveUser(this);
if (result) {
SuccessResponse response = new SuccessResponse(getCommandName());
this.setResponseObject(response);
} else {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the user to a new account");
}
}
}

View File

@ -27,6 +27,7 @@ import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
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.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.command.user.region.ListRegionsCmd;
@ -110,10 +111,17 @@ public interface RegionService {
*/
boolean deleteUser(DeleteUserCmd deleteUserCmd);
/**
* Deletes user by Id
* @param moveUserCmd
* @return true if delete was successful, false otherwise
*/
boolean moveUser(MoveUserCmd moveUserCmd);
/**
* update an existing domain
*
* @param cmd
* @param updateDomainCmd
* - the command containing domainId and new domainName
* @return Domain object if the command succeeded
*/

View File

@ -126,7 +126,25 @@ public class UserVO implements User, Identity, InternalIdentity {
this.source = source;
}
@Override
public UserVO(UserVO user) {
this.setAccountId(user.getAccountId());
this.setUsername(user.getUsername());
this.setPassword(user.getPassword());
this.setFirstname(user.getFirstname());
this.setLastname(user.getLastname());
this.setEmail(user.getEmail());
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());
this.setState(user.getState());
}
@Override
public long getId() {
return id;
}

View File

@ -25,6 +25,7 @@ import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
@ -311,6 +312,10 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
return false;
}
@Override public boolean moveUser(MoveUserCmd moveUserCmd) {
return false;
}
@Override
public boolean deleteUserAccount(long arg0) {
// TODO Auto-generated method stub

View File

@ -82,8 +82,13 @@
<cs.powermock.version>1.5.3</cs.powermock.version>
<cs.aws.sdk.version>1.3.22</cs.aws.sdk.version>
<cs.lang.version>2.6</cs.lang.version>
<cs.commons-io.version>2.5</cs.commons-io.version>
<cs.reflections.version>0.9.8</cs.reflections.version>
<cs.commons-lang3.version>3.6</cs.commons-lang3.version>
<cs.commons-io.version>2.6</cs.commons-io.version>
<cs.commons-fileupload.version>1.3.3</cs.commons-fileupload.version>
<cs.commons-collections.version>4.1</cs.commons-collections.version>
<cs.commons-validator.version>1.6</cs.commons-validator.version>
<cs.reflections.version>0.9.11</cs.reflections.version>
<cs.javassist.version>3.22.0-GA</cs.javassist.version>
<cs.java-ipv6.version>0.10</cs.java-ipv6.version>
<cs.replace.properties>build/replace.properties</cs.replace.properties>
<cs.libvirt-java.version>0.5.1</cs.libvirt-java.version>

View File

@ -221,6 +221,7 @@ 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.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.RegisterCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
@ -2680,6 +2681,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(GetUserCmd.class);
cmdList.add(ListUsersCmd.class);
cmdList.add(LockUserCmd.class);
cmdList.add(MoveUserCmd.class);
cmdList.add(RegisterCmd.class);
cmdList.add(UpdateUserCmd.class);
cmdList.add(CreateVlanIpRangeCmd.class);

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.apache.cloudstack.acl.ControlledEntity;
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;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import com.cloud.api.query.vo.ControlledViewEntity;
@ -152,10 +153,17 @@ public interface AccountManager extends AccountService {
*/
boolean deleteUser(DeleteUserCmd deleteUserCmd);
/**
* moves a user to another account within the same domain
* @param moveUserCmd
* @return true if the user was successfully moved
*/
boolean moveUser(MoveUserCmd moveUserCmd);
/**
* Update a user by userId
*
* @param userId
* @param cmd
* @return UserAccount object
*/
UserAccount updateUser(UpdateUserCmd cmd);

View File

@ -16,6 +16,28 @@
// under the License.
package com.cloud.user;
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.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.api.ApiDBUtils;
import com.cloud.api.query.vo.ControlledViewEntity;
import com.cloud.configuration.Config;
@ -128,6 +150,7 @@ 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.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.context.CallContext;
@ -142,27 +165,6 @@ 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.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
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;
@Local(value = {AccountManager.class, AccountService.class})
public class AccountManagerImpl extends ManagerBase implements AccountManager, Manager {
public static final Logger s_logger = Logger.getLogger(AccountManagerImpl.class);
@ -1662,29 +1664,89 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override
@ActionEvent(eventType = EventTypes.EVENT_USER_DELETE, eventDescription = "deleting User")
public boolean deleteUser(DeleteUserCmd deleteUserCmd) {
long id = deleteUserCmd.getId();
UserVO user = _userDao.findById(id);
if (user == null) {
throw new InvalidParameterValueException("The specified user doesn't exist in the system");
}
UserVO user = getValidUserVO(deleteUserCmd.getId());
Account account = _accountDao.findById(user.getAccountId());
// don't allow to delete the user from the account of type Project
checkAccountAndAccess(user, account);
return _userDao.remove(deleteUserCmd.getId());
}
@ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account")
public boolean moveUser(final MoveUserCmd cmd) {
final UserVO user = getValidUserVO(cmd.getId());
Account oldAccount = _accountDao.findById(user.getAccountId());
checkAccountAndAccess(user, oldAccount);
long domainId = oldAccount.getDomainId();
final long newAccountId = getNewAccountId(cmd, domainId);
if(newAccountId == user.getAccountId()) {
// could do a not silent fail but the objective of the user is reached
return true; // no need to create a new user object for this user
}
return Transaction.execute(new TransactionCallback<Boolean>() {
@Override
public Boolean doInTransaction(TransactionStatus status) {
UserVO newUser = new UserVO(user);
user.setExternalEntity(user.getUuid());
user.setUuid(UUID.randomUUID().toString());
_userDao.update(user.getId(),user);
newUser.setAccountId(newAccountId);
boolean success = _userDao.remove(cmd.getId());
UserVO persisted = _userDao.persist(newUser);
return success && persisted.getUuid().equals(user.getExternalEntity());
}
});
}
private long getNewAccountId(MoveUserCmd cmd, long domainId) {
Account newAccount = null;
if (StringUtils.isNotBlank(cmd.getAccountName())) {
if(s_logger.isDebugEnabled()) {
s_logger.debug("Getting id for account by name '" + cmd.getAccountName() + "' in domain " + domainId);
}
newAccount = _accountDao.findEnabledAccount(cmd.getAccountName(), domainId);
}
if (newAccount == null && cmd.getAccountId() != null) {
newAccount = _accountDao.findById(cmd.getAccountId());
}
if (newAccount == null) {
throw new CloudRuntimeException("no account name or account id. this should have been caught before this point");
}
long newAccountId = newAccount.getAccountId();
if(newAccount.getDomainId() != domainId) {
// not in scope
throw new InvalidParameterValueException("moving a user from an account in one domain to an account in annother domain is not supported!");
}
return newAccountId;
}
private void checkAccountAndAccess(UserVO user, Account account) {
// don't allow to delete the user from the account of type Project
if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
throw new InvalidParameterValueException("Project users cannot be deleted or moved.");
}
checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
CallContext.current().putContextParameter(User.class, user.getUuid());
}
private UserVO getValidUserVO(long id) {
UserVO user = _userDao.findById(id);
if (user == null || user.getRemoved() != null) {
throw new InvalidParameterValueException("The specified user doesn't exist in the system");
}
// don't allow to delete default user (system and admin users)
if (user.isDefault()) {
throw new InvalidParameterValueException("The user is default and can't be removed");
throw new InvalidParameterValueException("The user is default and can't be (re)moved");
}
checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
CallContext.current().putContextParameter(User.class, user.getUuid());
return _userDao.remove(id);
return user;
}
protected class AccountCleanupTask extends ManagedContextRunnable {

View File

@ -21,6 +21,7 @@ import java.util.List;
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import com.cloud.domain.Domain;
@ -122,10 +123,17 @@ public interface RegionManager {
*/
boolean deleteUser(DeleteUserCmd deleteUserCmd);
/**
* Deletes user by Id
* @param moveUserCmd
* @return
*/
boolean moveUser(MoveUserCmd moveUserCmd);
/**
* update an existing domain
*
* @param cmd
* @param updateDomainCmd
* - the command containing domainId and new domainName
* @return Domain object if the command succeeded
*/
@ -142,7 +150,7 @@ public interface RegionManager {
/**
* Update a user by userId
*
* @param userId
* @param updateUserCmd
* @return UserAccount object
*/
UserAccount updateUser(UpdateUserCmd updateUserCmd);
@ -150,7 +158,7 @@ public interface RegionManager {
/**
* Disables a user by userId
*
* @param userId
* @param id
* - the userId
* @return UserAccount object
*/

View File

@ -25,6 +25,7 @@ import javax.ejb.Local;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Component;
@ -226,6 +227,14 @@ public class RegionManagerImpl extends ManagerBase implements RegionManager, Man
return _accountMgr.deleteUser(cmd);
}
/**
* {@inheritDoc}
*/
@Override
public boolean moveUser(MoveUserCmd cmd) {
return _accountMgr.moveUser(cmd);
}
/**
* {@inheritDoc}
*/

View File

@ -35,6 +35,7 @@ import org.apache.cloudstack.api.command.admin.domain.UpdateDomainCmd;
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.MoveUserCmd;
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
import org.apache.cloudstack.api.command.user.region.ListRegionsCmd;
@ -153,6 +154,14 @@ public class RegionServiceImpl extends ManagerBase implements RegionService, Man
return _regionMgr.deleteUser(cmd);
}
/**
* {@inheritDoc}
*/
@Override
public boolean moveUser(MoveUserCmd cmd) {
return _regionMgr.moveUser(cmd);
}
/**
* {@inheritDoc}
*/

View File

@ -22,6 +22,7 @@ import java.util.Map;
import javax.ejb.Local;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
import org.springframework.stereotype.Component;
import org.apache.cloudstack.acl.ControlledEntity;
@ -119,6 +120,10 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco
return false;
}
@Override public boolean moveUser(MoveUserCmd moveUserCmd) {
return false;
}
@Override
public boolean isAdmin(Long accountId) {
// TODO Auto-generated method stub

File diff suppressed because it is too large Load Diff

View File

@ -70,6 +70,15 @@ test_data = {
"url": 'http://10.147.60.15/42xescauto spaces/42xesc Clusters',
"clustername": 'VMWare Cluster with Space in DC name',
},
"user": {
"email": "user@test.com",
"firstname": "User",
"lastname": "User",
"username": "User",
# Random characters are appended for unique
# username
"password": "fr3sca",
},
"small": {
"displayname": "testserver",
"username": "root",

View File

@ -268,6 +268,19 @@ class User:
cmd.id = self.id
apiclient.deleteUser(cmd)
def move(self, api_client, dest_accountid = None, dest_account = None, domain= None):
if all([dest_account, dest_accountid]) is None:
raise Exception("Please add either destination account or destination account ID.")
cmd = moveUser.moveUserCmd()
cmd.id = self.id
cmd.accountid = dest_accountid
cmd.account = dest_account
cmd.domain = domain
return api_client.moveUser(cmd)
@classmethod
def list(cls, apiclient, **kwargs):
"""Lists users and provides detailed account information for