Skip to content

Commit 6724a47

Browse files
authored
CLOUDSTACK-10121 moveUser (#2301)
* internal service call for moveUser * expose moveUser as API * move uuid to external entity
1 parent e27b3e1 commit 6724a47

17 files changed

Lines changed: 440 additions & 16 deletions

File tree

api/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@
5656
<artifactId>cloud-framework-ca</artifactId>
5757
<version>${project.version}</version>
5858
</dependency>
59+
<dependency>
60+
<groupId>org.apache.commons</groupId>
61+
<artifactId>commons-lang3</artifactId>
62+
<version>${cs.commons-lang3.version}</version>
63+
</dependency>
5964
</dependencies>
6065
<build>
6166
<plugins>

api/src/com/cloud/event/EventTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ public class EventTypes {
198198
public static final String EVENT_USER_CREATE = "USER.CREATE";
199199
public static final String EVENT_USER_DELETE = "USER.DELETE";
200200
public static final String EVENT_USER_DISABLE = "USER.DISABLE";
201+
public static final String EVENT_USER_MOVE = "USER.MOVE";
201202
public static final String EVENT_USER_UPDATE = "USER.UPDATE";
202203
public static final String EVENT_USER_ENABLE = "USER.ENABLE";
203204
public static final String EVENT_USER_LOCK = "USER.LOCK";
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.admin.user;
18+
19+
import com.cloud.user.Account;
20+
import com.cloud.user.User;
21+
import com.google.common.base.Preconditions;
22+
import org.apache.cloudstack.acl.RoleType;
23+
import org.apache.cloudstack.api.APICommand;
24+
import org.apache.cloudstack.api.ApiConstants;
25+
import org.apache.cloudstack.api.ApiErrorCode;
26+
import org.apache.cloudstack.api.BaseCmd;
27+
import org.apache.cloudstack.api.Parameter;
28+
import org.apache.cloudstack.api.ServerApiException;
29+
import org.apache.cloudstack.api.response.AccountResponse;
30+
import org.apache.cloudstack.api.response.SuccessResponse;
31+
import org.apache.cloudstack.api.response.UserResponse;
32+
import org.apache.cloudstack.context.CallContext;
33+
import org.apache.cloudstack.region.RegionService;
34+
import org.apache.commons.lang3.ObjectUtils;
35+
import org.apache.log4j.Logger;
36+
37+
import javax.inject.Inject;
38+
39+
@APICommand(name = "moveUser",
40+
description = "Moves a user to another account",
41+
responseObject = SuccessResponse.class,
42+
requestHasSensitiveInfo = false,
43+
responseHasSensitiveInfo = false,
44+
since = "4.11",
45+
authorized = {RoleType.Admin})
46+
public class MoveUserCmd extends BaseCmd {
47+
public static final Logger s_logger = Logger.getLogger(UpdateUserCmd.class.getName());
48+
49+
public static final String APINAME = "moveUser";
50+
51+
/////////////////////////////////////////////////////
52+
//////////////// API parameters /////////////////////
53+
/////////////////////////////////////////////////////
54+
@Parameter(name = ApiConstants.ID,
55+
type = CommandType.UUID,
56+
entityType = UserResponse.class,
57+
required = true,
58+
description = "id of the user to be deleted")
59+
private Long id;
60+
61+
@Parameter(name = ApiConstants.ACCOUNT,
62+
type = CommandType.STRING,
63+
description = "Creates the user under the specified account. If no account is specified, the username will be used as the account name.")
64+
private String accountName;
65+
66+
@Parameter(name = ApiConstants.ACCOUNT_ID,
67+
type = CommandType.UUID,
68+
entityType = AccountResponse.class,
69+
description = "Creates the user under the specified domain. Has to be accompanied with the account parameter")
70+
private Long accountId;
71+
72+
@Inject
73+
RegionService _regionService;
74+
75+
/////////////////////////////////////////////////////
76+
/////////////////// Accessors ///////////////////////
77+
/////////////////////////////////////////////////////
78+
79+
public Long getId() {
80+
return id;
81+
}
82+
83+
public String getAccountName() {
84+
return accountName;
85+
}
86+
87+
public Long getAccountId() {
88+
return accountId;
89+
}
90+
91+
/////////////////////////////////////////////////////
92+
/////////////// API Implementation///////////////////
93+
/////////////////////////////////////////////////////
94+
95+
@Override
96+
public String getCommandName() {
97+
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
98+
}
99+
100+
@Override
101+
public long getEntityOwnerId() {
102+
User user = _entityMgr.findById(User.class, getId());
103+
if (user != null) {
104+
return user.getAccountId();
105+
}
106+
107+
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
108+
}
109+
110+
@Override
111+
public void execute() {
112+
Preconditions.checkNotNull(getId(),"I have to have an user to move!");
113+
Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!");
114+
115+
CallContext.current().setEventDetails("UserId: " + getId());
116+
boolean result =
117+
_regionService.moveUser(this);
118+
if (result) {
119+
SuccessResponse response = new SuccessResponse(getCommandName());
120+
this.setResponseObject(response);
121+
} else {
122+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to move the user to a new account");
123+
}
124+
}
125+
126+
}

api/src/org/apache/cloudstack/region/RegionService.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
2828
import org.apache.cloudstack.api.command.admin.user.DisableUserCmd;
2929
import org.apache.cloudstack.api.command.admin.user.EnableUserCmd;
30+
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
3031
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
3132
import org.apache.cloudstack.api.command.user.region.ListRegionsCmd;
3233

@@ -110,10 +111,17 @@ public interface RegionService {
110111
*/
111112
boolean deleteUser(DeleteUserCmd deleteUserCmd);
112113

114+
/**
115+
* Deletes user by Id
116+
* @param moveUserCmd
117+
* @return true if delete was successful, false otherwise
118+
*/
119+
boolean moveUser(MoveUserCmd moveUserCmd);
120+
113121
/**
114122
* update an existing domain
115123
*
116-
* @param cmd
124+
* @param updateDomainCmd
117125
* - the command containing domainId and new domainName
118126
* @return Domain object if the command succeeded
119127
*/

engine/schema/src/com/cloud/user/UserVO.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,25 @@ public UserVO(long accountId, String username, String password, String firstName
127127
this.source = source;
128128
}
129129

130-
@Override
130+
public UserVO(UserVO user) {
131+
this.setAccountId(user.getAccountId());
132+
this.setUsername(user.getUsername());
133+
this.setPassword(user.getPassword());
134+
this.setFirstname(user.getFirstname());
135+
this.setLastname(user.getLastname());
136+
this.setEmail(user.getEmail());
137+
this.setTimezone(user.getTimezone());
138+
this.setUuid(user.getUuid());
139+
this.setSource(user.getSource());
140+
this.setApiKey(user.getApiKey());
141+
this.setSecretKey(user.getSecretKey());
142+
this.setExternalEntity(user.getExternalEntity());
143+
this.setRegistered(user.isRegistered());
144+
this.setRegistrationToken(user.getRegistrationToken());
145+
this.setState(user.getState());
146+
}
147+
148+
@Override
131149
public long getId() {
132150
return id;
133151
}

plugins/network-elements/juniper-contrail/test/org/apache/cloudstack/network/contrail/management/MockAccountManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import javax.naming.ConfigurationException;
2626

2727
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
28+
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
2829
import org.apache.cloudstack.framework.config.ConfigKey;
2930
import org.apache.log4j.Logger;
3031

@@ -316,6 +317,10 @@ public boolean deleteUser(DeleteUserCmd arg0) {
316317
return false;
317318
}
318319

320+
@Override public boolean moveUser(MoveUserCmd moveUserCmd) {
321+
return false;
322+
}
323+
319324
@Override
320325
public boolean deleteUserAccount(long arg0) {
321326
// TODO Auto-generated method stub

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
<cs.aws.sdk.version>1.11.213</cs.aws.sdk.version>
9595
<cs.jackson.version>2.9.2</cs.jackson.version>
9696
<cs.lang.version>2.6</cs.lang.version>
97-
<cs.commons-lang3.version>3.4</cs.commons-lang3.version>
97+
<cs.commons-lang3.version>3.6</cs.commons-lang3.version>
9898
<cs.commons-io.version>2.6</cs.commons-io.version>
9999
<cs.commons-fileupload.version>1.3.3</cs.commons-fileupload.version>
100100
<cs.commons-collections.version>4.1</cs.commons-collections.version>

server/src/com/cloud/server/ManagementServerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@
229229
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
230230
import org.apache.cloudstack.api.command.admin.user.ListUsersCmd;
231231
import org.apache.cloudstack.api.command.admin.user.LockUserCmd;
232+
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
232233
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
233234
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
234235
import org.apache.cloudstack.api.command.admin.vlan.CreateVlanIpRangeCmd;
@@ -2672,6 +2673,7 @@ public List<Class<?>> getCommands() {
26722673
cmdList.add(GetUserCmd.class);
26732674
cmdList.add(ListUsersCmd.class);
26742675
cmdList.add(LockUserCmd.class);
2676+
cmdList.add(MoveUserCmd.class);
26752677
cmdList.add(RegisterCmd.class);
26762678
cmdList.add(UpdateUserCmd.class);
26772679
cmdList.add(CreateVlanIpRangeCmd.class);

server/src/com/cloud/user/AccountManager.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.apache.cloudstack.acl.ControlledEntity;
2424
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
2525
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
26+
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
2627
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
2728

2829
import com.cloud.api.query.vo.ControlledViewEntity;
@@ -155,10 +156,17 @@ void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledViewEntity> s
155156
*/
156157
boolean deleteUser(DeleteUserCmd deleteUserCmd);
157158

159+
/**
160+
* moves a user to another account within the same domain
161+
* @param moveUserCmd
162+
* @return true if the user was successfully moved
163+
*/
164+
boolean moveUser(MoveUserCmd moveUserCmd);
165+
158166
/**
159167
* Update a user by userId
160168
*
161-
* @param userId
169+
* @param cmd
162170
* @return UserAccount object
163171
*/
164172
UserAccount updateUser(UpdateUserCmd cmd);

server/src/com/cloud/user/AccountManagerImpl.java

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.apache.cloudstack.api.command.admin.account.UpdateAccountCmd;
5353
import org.apache.cloudstack.api.command.admin.user.DeleteUserCmd;
5454
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
55+
import org.apache.cloudstack.api.command.admin.user.MoveUserCmd;
5556
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
5657
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
5758
import org.apache.cloudstack.context.CallContext;
@@ -1686,29 +1687,89 @@ public Boolean doInTransaction(TransactionStatus status) {
16861687
@Override
16871688
@ActionEvent(eventType = EventTypes.EVENT_USER_DELETE, eventDescription = "deleting User")
16881689
public boolean deleteUser(DeleteUserCmd deleteUserCmd) {
1689-
long id = deleteUserCmd.getId();
1690+
UserVO user = getValidUserVO(deleteUserCmd.getId());
16901691

1691-
UserVO user = _userDao.findById(id);
1692+
Account account = _accountDao.findById(user.getAccountId());
16921693

1693-
if (user == null) {
1694-
throw new InvalidParameterValueException("The specified user doesn't exist in the system");
1694+
// don't allow to delete the user from the account of type Project
1695+
checkAccountAndAccess(user, account);
1696+
return _userDao.remove(deleteUserCmd.getId());
1697+
}
1698+
1699+
@ActionEvent(eventType = EventTypes.EVENT_USER_MOVE, eventDescription = "moving User to a new account")
1700+
public boolean moveUser(MoveUserCmd cmd) {
1701+
UserVO user = getValidUserVO(cmd.getId());
1702+
Account oldAccount = _accountDao.findById(user.getAccountId());
1703+
checkAccountAndAccess(user, oldAccount);
1704+
long domainId = oldAccount.getDomainId();
1705+
1706+
long newAccountId = getNewAccountId(cmd, domainId);
1707+
1708+
if(newAccountId == user.getAccountId()) {
1709+
// could do a not silent fail but the objective of the user is reached
1710+
return true; // no need to create a new user object for this user
16951711
}
1712+
return Transaction.execute(new TransactionCallback<Boolean>() {
1713+
@Override
1714+
public Boolean doInTransaction(TransactionStatus status) {
1715+
UserVO newUser = new UserVO(user);
1716+
user.setExternalEntity(user.getUuid());
1717+
user.setUuid(UUID.randomUUID().toString());
1718+
_userDao.update(user.getId(),user);
1719+
newUser.setAccountId(newAccountId);
1720+
boolean success = _userDao.remove(cmd.getId());
1721+
UserVO persisted = _userDao.persist(newUser);
1722+
return success && persisted.getUuid().equals(user.getExternalEntity());
1723+
}
1724+
});
1725+
}
16961726

1697-
Account account = _accountDao.findById(user.getAccountId());
1727+
private long getNewAccountId(MoveUserCmd cmd, long domainId) {
1728+
Account newAccount = null;
1729+
if (StringUtils.isNotBlank(cmd.getAccountName())) {
1730+
if(s_logger.isDebugEnabled()) {
1731+
s_logger.debug("Getting id for account by name '" + cmd.getAccountName() + "' in domain " + domainId);
1732+
}
1733+
newAccount = _accountDao.findEnabledAccount(cmd.getAccountName(), domainId);
1734+
}
1735+
if (newAccount == null && cmd.getAccountId() != null) {
1736+
newAccount = _accountDao.findById(cmd.getAccountId());
1737+
}
1738+
if (newAccount == null) {
1739+
throw new CloudRuntimeException("no account name or account id. this should have been caught before this point");
1740+
}
1741+
long newAccountId = newAccount.getAccountId();
1742+
1743+
if(newAccount.getDomainId() != domainId) {
1744+
// not in scope
1745+
throw new InvalidParameterValueException("moving a user from an account in one domain to an account in annother domain is not supported!");
1746+
}
1747+
return newAccountId;
1748+
}
16981749

1750+
private void checkAccountAndAccess(UserVO user, Account account) {
16991751
// don't allow to delete the user from the account of type Project
17001752
if (account.getType() == Account.ACCOUNT_TYPE_PROJECT) {
1753+
throw new InvalidParameterValueException("Project users cannot be deleted or moved.");
1754+
}
1755+
1756+
checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
1757+
CallContext.current().putContextParameter(User.class, user.getUuid());
1758+
}
1759+
1760+
private UserVO getValidUserVO(long id) {
1761+
UserVO user = _userDao.findById(id);
1762+
1763+
if (user == null || user.getRemoved() != null) {
17011764
throw new InvalidParameterValueException("The specified user doesn't exist in the system");
17021765
}
17031766

17041767
// don't allow to delete default user (system and admin users)
17051768
if (user.isDefault()) {
1706-
throw new InvalidParameterValueException("The user is default and can't be removed");
1769+
throw new InvalidParameterValueException("The user is default and can't be (re)moved");
17071770
}
17081771

1709-
checkAccess(CallContext.current().getCallingAccount(), AccessType.OperateEntry, true, account);
1710-
CallContext.current().putContextParameter(User.class, user.getUuid());
1711-
return _userDao.remove(id);
1772+
return user;
17121773
}
17131774

17141775
protected class AccountCleanupTask extends ManagedContextRunnable {

0 commit comments

Comments
 (0)