Skip to content

Commit 6510400

Browse files
committed
1. Allow enforcing password change while creating user
2. Admin can enforce password change on next login with out resetting password
1 parent 5e30f65 commit 6510400

File tree

6 files changed

+79
-14
lines changed

6 files changed

+79
-14
lines changed

api/src/main/java/com/cloud/user/AccountService.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ UserAccount createUserAccount(String userName, String password, String firstName
5858

5959
User getSystemUser();
6060

61-
User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID);
61+
User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone,
62+
String accountName, Long domainId, String userUUID, boolean isPasswordChangeRequired);
6263

6364
User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID,
6465
User.Source source);

api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.cloudstack.api.response.DomainResponse;
2727
import org.apache.cloudstack.api.response.UserResponse;
2828
import org.apache.cloudstack.context.CallContext;
29+
import org.apache.commons.lang.BooleanUtils;
2930
import org.apache.commons.lang3.StringUtils;
3031

3132
import com.cloud.user.Account;
@@ -78,6 +79,12 @@ public class CreateUserCmd extends BaseCmd {
7879
@Parameter(name = ApiConstants.USER_ID, type = CommandType.STRING, description = "User UUID, required for adding account from external provisioning system")
7980
private String userUUID;
8081

82+
@Parameter(name = ApiConstants.PASSWORD_CHANGE_REQUIRED,
83+
type = CommandType.BOOLEAN,
84+
description = "Provide true to mandate the User to reset password on next login.",
85+
since = "4.23.0")
86+
private Boolean passwordChangeRequired;
87+
8188
/////////////////////////////////////////////////////
8289
/////////////////// Accessors ///////////////////////
8390
/////////////////////////////////////////////////////
@@ -118,6 +125,10 @@ public String getUserUUID() {
118125
return userUUID;
119126
}
120127

128+
public Boolean isPasswordChangeRequired() {
129+
return BooleanUtils.isTrue(passwordChangeRequired);
130+
}
131+
121132
/////////////////////////////////////////////////////
122133
/////////////// API Implementation///////////////////
123134
/////////////////////////////////////////////////////
@@ -147,7 +158,7 @@ public void execute() {
147158
CallContext.current().setEventDetails("UserName: " + getUserName() + ", FirstName :" + getFirstName() + ", LastName: " + getLastName());
148159
User user =
149160
_accountService.createUser(getUserName(), getPassword(), getFirstName(), getLastName(), getEmail(), getTimezone(), getAccountName(), getDomainId(),
150-
getUserUUID());
161+
getUserUUID(), isPasswordChangeRequired());
151162
if (user != null) {
152163
UserResponse response = _responseGenerator.createUserResponse(user);
153164
response.setResponseName(getCommandName());

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

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,12 +1510,24 @@ private List<APIChecker> getEnabledApiCheckers() {
15101510
@Override
15111511
@ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")
15121512
public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID,
1513-
User.Source source) {
1513+
User.Source source) {
1514+
return createUser(userName, password, firstName, lastName, email, timeZone, accountName, domainId, userUUID, source, false);
1515+
}
1516+
1517+
1518+
@ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")
1519+
public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID,
1520+
User.Source source, boolean isPasswordChangeRequired) {
15141521
// default domain to ROOT if not specified
15151522
if (domainId == null) {
15161523
domainId = Domain.ROOT_DOMAIN;
15171524
}
15181525

1526+
if (isPasswordChangeRequired && (source == User.Source.SAML2 || source == User.Source.SAML2DISABLED || source == User.Source.LDAP)) {
1527+
logger.warn("Enforcing password change is not permitted for source [{}].", source);
1528+
throw new InvalidParameterValueException("CloudStack does not support enforcing password change for SAML or LDAP users.");
1529+
}
1530+
15191531
Domain domain = _domainMgr.getDomain(domainId);
15201532
if (domain == null) {
15211533
throw new CloudRuntimeException("The domain " + domainId + " does not exist; unable to create user");
@@ -1546,14 +1558,21 @@ public UserVO createUser(String userName, String password, String firstName, Str
15461558
verifyCallerPrivilegeForUserOrAccountOperations(account);
15471559
UserVO user;
15481560
user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source);
1561+
if (isPasswordChangeRequired) {
1562+
long callerAccountId = CallContext.current().getCallingAccountId();
1563+
if ((isRootAdmin(callerAccountId) || isDomainAdmin(callerAccountId))) {
1564+
_userDetailsDao.addDetail(user.getId(), PasswordChangeRequired, "true", false);
1565+
}
1566+
}
15491567
return user;
15501568
}
15511569

15521570
@Override
15531571
@ActionEvent(eventType = EventTypes.EVENT_USER_CREATE, eventDescription = "creating User")
1554-
public UserVO createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID) {
1572+
public UserVO createUser(String userName, String password, String firstName, String lastName, String email,
1573+
String timeZone, String accountName, Long domainId, String userUUID, boolean isPasswordChangeRequired) {
15551574

1556-
return createUser(userName, password, firstName, lastName, email, timeZone, accountName, domainId, userUUID, User.Source.UNKNOWN);
1575+
return createUser(userName, password, firstName, lastName, email, timeZone, accountName, domainId, userUUID, User.Source.UNKNOWN, isPasswordChangeRequired);
15571576
}
15581577

15591578
@Override
@@ -1587,22 +1606,29 @@ public UserAccount updateUser(UpdateUserCmd updateUserCmd) {
15871606
if (mandate2FA != null && mandate2FA) {
15881607
user.setUser2faEnabled(true);
15891608
}
1590-
_userDao.update(user.getId(), user);
15911609
updatePasswordChangeRequired(caller, updateUserCmd, user);
1610+
_userDao.update(user.getId(), user);
15921611
return _userAccountDao.findById(user.getId());
15931612
}
15941613

15951614
private void updatePasswordChangeRequired(User caller, UpdateUserCmd updateUserCmd, UserVO user) {
1596-
if (StringUtils.isNotBlank(updateUserCmd.getPassword())) {
1597-
boolean isCallerSameAsUser = user.getId() == caller.getId();
1598-
boolean isPasswordResetRequired = updateUserCmd.isPasswordChangeRequired() && !isCallerSameAsUser;
1599-
// Admins only can enforce passwordChangeRequired for user
1600-
if (isRootAdmin(caller.getAccountId()) || isDomainAdmin(caller.getAccountId())) {
1601-
if (isPasswordResetRequired) {
1602-
_userDetailsDao.addDetail(user.getId(), PasswordChangeRequired, "true", false);
1603-
}
1615+
User.Source userSource = user.getSource();
1616+
if ((userSource == User.Source.SAML2 || userSource == User.Source.SAML2DISABLED || userSource == User.Source.LDAP)
1617+
&& updateUserCmd.isPasswordChangeRequired()) {
1618+
logger.warn("Enforcing password change is not permitted for source [{}].", user.getSource());
1619+
throw new InvalidParameterValueException("CloudStack does not support enforcing password change for SAML or LDAP users.");
1620+
}
1621+
1622+
boolean isCallerSameAsUser = user.getId() == caller.getId();
1623+
boolean isPasswordResetRequired = updateUserCmd.isPasswordChangeRequired() && !isCallerSameAsUser;
1624+
// Admins only can enforce passwordChangeRequired for user
1625+
if (isRootAdmin(caller.getAccountId()) || isDomainAdmin(caller.getAccountId())) {
1626+
if (isPasswordResetRequired) {
1627+
_userDetailsDao.addDetail(user.getId(), PasswordChangeRequired, "true", false);
16041628
}
1629+
}
16051630

1631+
if (StringUtils.isNotBlank(updateUserCmd.getPassword())) {
16061632
// Remove passwordChangeRequired if user updating own pwd or admin has not enforced it
16071633
if (isCallerSameAsUser || !isPasswordResetRequired) {
16081634
_userDetailsDao.removeDetail(user.getId(), PasswordChangeRequired);

ui/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@
530530
"label.change.ipaddress": "Change IP address for NIC",
531531
"label.change.disk.offering": "Change disk offering",
532532
"label.change.offering.for.volume": "Change disk offering for the volume",
533+
"label.change.password.enforce": "Enforce password change",
533534
"label.change.password.onlogin": "User must change password at next login",
534535
"label.change.service.offering": "Change service offering",
535536
"label.character": "Character",

ui/src/config/section/user.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,23 @@ export default {
8282
popup: true,
8383
component: shallowRef(defineAsyncComponent(() => import('@/views/iam/EditUser.vue')))
8484
},
85+
{
86+
api: 'updateUser',
87+
icon: 'redo-outlined',
88+
label: 'label.change.password.enforce',
89+
dataView: true,
90+
args: ['passwordchangerequired'],
91+
mapping: {
92+
passwordchangerequired: {
93+
value: (record) => { return true }
94+
}
95+
},
96+
popup: true,
97+
show: (record, store) => {
98+
return ['Admin', 'DomainAdmin'].includes(store.userInfo.roletype) &&
99+
!record.isdefault && (store.userInfo.id !== record.id)
100+
}
101+
},
85102
{
86103
api: 'updateUser',
87104
icon: 'key-outlined',

ui/src/views/iam/AddUser.vue

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@
147147
</a-select-option>
148148
</a-select>
149149
</a-form-item>
150+
<a-form-item v-if="isAdminOrDomainAdmin() && !samlAllowed" name="passwordChangeRequired" ref="passwordChangeRequired">
151+
<a-checkbox v-model:checked="form.passwordChangeRequired">
152+
{{ $t('label.change.password.onlogin') }}
153+
</a-checkbox>
154+
</a-form-item>
150155
<div v-if="samlAllowed">
151156
<a-form-item name="samlenable" ref="samlenable" :label="$t('label.samlenable')">
152157
<a-switch v-model:checked="form.samlenable" />
@@ -384,6 +389,10 @@ export default {
384389
if (this.isValidValueForKey(rawParams, 'timezone') && rawParams.timezone.length > 0) {
385390
params.timezone = rawParams.timezone
386391
}
392+
console.log('rawParams.passwordChangeRequired', rawParams.passwordChangeRequired)
393+
if (this.isAdminOrDomainAdmin() && rawParams.passwordChangeRequired === true) {
394+
params.passwordchangerequired = rawParams.passwordChangeRequired
395+
}
387396
388397
return postAPI('createUser', params)
389398
},

0 commit comments

Comments
 (0)