Skip to content

Commit 0809f3a

Browse files
authored
chore: Add update-local-user command to SSC access-control (#928) (#931)
feat: `fcli ssc access-control update-local-user`: New command for updating a local SSC user Co-authored-by: gilseara
1 parent e4462ab commit 0809f3a

3 files changed

Lines changed: 169 additions & 0 deletions

File tree

fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCAccessControlCommands.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
SSCPermissionGetCommand.class,
3333
SSCPermissionListCommand.class,
3434
SSCUserCreateLocalCommand.class,
35+
SSCUserUpdateLocalCommand.class,
3536
SSCUserDeleteCommand.class,
3637
SSCUserGetCommand.class,
3738
SSCUserListCommand.class,
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright 2021-2026 Open Text.
3+
*
4+
* The only warranties for products and services of Open Text
5+
* and its affiliates and licensors ("Open Text") are as may
6+
* be set forth in the express warranty statements accompanying
7+
* such products and services. Nothing herein should be construed
8+
* as constituting an additional warranty. Open Text shall not be
9+
* liable for technical or editorial errors or omissions contained
10+
* herein. The information contained herein is subject to change
11+
* without notice.
12+
*/
13+
package com.fortify.cli.ssc.access_control.cli.cmd;
14+
15+
import java.util.ArrayList;
16+
import java.util.HashSet;
17+
import java.util.Set;
18+
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.node.ArrayNode;
21+
import com.fasterxml.jackson.databind.node.ObjectNode;
22+
import com.fortify.cli.common.cli.util.CommandGroup;
23+
import com.fortify.cli.common.exception.FcliSimpleException;
24+
import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins;
25+
import com.fortify.cli.common.output.transform.IActionCommandResultSupplier;
26+
import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCJsonNodeOutputCommand;
27+
import com.fortify.cli.ssc._common.rest.ssc.SSCUrls;
28+
import com.fortify.cli.ssc.access_control.cli.mixin.SSCUserResolverMixin;
29+
30+
import kong.unirest.UnirestInstance;
31+
import lombok.Getter;
32+
import picocli.CommandLine.Command;
33+
import picocli.CommandLine.Mixin;
34+
import picocli.CommandLine.Option;
35+
36+
@Command(name = "update-local-user") @CommandGroup("user")
37+
public class SSCUserUpdateLocalCommand extends AbstractSSCJsonNodeOutputCommand implements IActionCommandResultSupplier {
38+
@Getter @Mixin private OutputHelperMixins.DetailsNoQuery outputHelper;
39+
40+
@Mixin private SSCUserResolverMixin.PositionalParameterSingle userResolver;
41+
42+
@Option(names = {"--firstname"})
43+
private String firstName;
44+
@Option(names = {"--lastname"})
45+
private String lastName;
46+
@Option(names = {"--email"})
47+
private String email;
48+
@Option(names = {"--password"})
49+
private String password;
50+
@Option(names = {"--password-never-expires", "--pne"})
51+
private Boolean pwNeverExpires;
52+
@Option(names = {"--require-password-change", "--rpc"})
53+
private Boolean requirePwChange;
54+
@Option(names = {"--suspend"})
55+
private Boolean suspend;
56+
@Option(names = {"--roles"}, split = ",")
57+
private ArrayList<String> roles;
58+
@Option(names = {"--add-roles"}, split = ",")
59+
private ArrayList<String> addRoles;
60+
@Option(names = {"--rm-roles"}, split = ",")
61+
private ArrayList<String> rmRoles;
62+
63+
@Override
64+
public JsonNode getJsonNode(UnirestInstance unirest) {
65+
String userId = resolveUserId(unirest);
66+
ObjectNode userData = getLocalUser(unirest, userId);
67+
setUserAttributes(userData);
68+
setUserRoles(userData);
69+
return unirest.put(SSCUrls.LOCAL_USER(userId))
70+
.body(userData)
71+
.asObject(JsonNode.class).getBody();
72+
}
73+
74+
private String resolveUserId(UnirestInstance unirest) {
75+
ArrayNode authEntities = (ArrayNode) userResolver.getAuthEntityJsonNode(unirest);
76+
if (authEntities.size() != 1) {
77+
throw new FcliSimpleException(
78+
"User specification matched %d users; please provide a unique user id", authEntities.size());
79+
}
80+
return authEntities.get(0).get("id").asText();
81+
}
82+
83+
private ObjectNode getLocalUser(UnirestInstance unirest, String userId) {
84+
return (ObjectNode) unirest.get(SSCUrls.LOCAL_USER(userId))
85+
.asObject(JsonNode.class).getBody().get("data");
86+
}
87+
88+
private void setUserAttributes(ObjectNode userData) {
89+
if (firstName != null) { userData.put("firstName", firstName); }
90+
if (lastName != null) { userData.put("lastName", lastName); }
91+
if (email != null) { userData.put("email", email); }
92+
if (password != null) { userData.put("clearPassword", password); }
93+
if (pwNeverExpires != null) { userData.put("passwordNeverExpires", pwNeverExpires); }
94+
if (requirePwChange != null) { userData.put("requirePasswordChange", requirePwChange); }
95+
if (suspend != null) { userData.put("suspended", suspend); }
96+
}
97+
98+
private void setUserRoles(ObjectNode userData) {
99+
if (roles != null) {
100+
ArrayNode rolesArray = userData.putArray("roles");
101+
for (String roleId : roles) {
102+
rolesArray.addObject().put("id", roleId);
103+
}
104+
}
105+
if (addRoles != null) {
106+
addRolesToUser(userData);
107+
}
108+
if (rmRoles != null) {
109+
removeRolesFromUser(userData);
110+
}
111+
}
112+
113+
private void addRolesToUser(ObjectNode userData) {
114+
ArrayNode existingRoles = getOrCreateRolesArray(userData);
115+
Set<String> existingRoleIds = collectRoleIds(existingRoles);
116+
for (String roleId : addRoles) {
117+
if (!existingRoleIds.contains(roleId)) {
118+
existingRoles.addObject().put("id", roleId);
119+
}
120+
}
121+
}
122+
123+
private void removeRolesFromUser(ObjectNode userData) {
124+
ArrayNode existingRoles = (ArrayNode) userData.get("roles");
125+
if (existingRoles == null) { return; }
126+
Set<String> idsToRemove = new HashSet<>(rmRoles);
127+
ArrayNode filteredRoles = userData.putArray("roles");
128+
for (JsonNode role : existingRoles) {
129+
if (!idsToRemove.contains(role.get("id").asText())) {
130+
filteredRoles.add(role);
131+
}
132+
}
133+
}
134+
135+
private ArrayNode getOrCreateRolesArray(ObjectNode userData) {
136+
ArrayNode existing = (ArrayNode) userData.get("roles");
137+
return existing != null ? existing : userData.putArray("roles");
138+
}
139+
140+
private Set<String> collectRoleIds(ArrayNode rolesArray) {
141+
Set<String> ids = new HashSet<>();
142+
for (JsonNode role : rolesArray) {
143+
ids.add(role.get("id").asText());
144+
}
145+
return ids;
146+
}
147+
148+
@Override
149+
public String getActionCommandResult() {
150+
return "UPDATED";
151+
}
152+
153+
@Override
154+
public boolean isSingular() {
155+
return true;
156+
}
157+
}

fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,17 @@ fcli.ssc.access-control.create-local-user.password-never-expires = Causes the pa
273273
fcli.ssc.access-control.create-local-user.require-password-change = Requires the password of the user to be changed
274274
fcli.ssc.access-control.create-local-user.suspend = Causes the user to automatically be su
275275
fcli.ssc.access-control.create-local-user.username = The username of the user
276+
fcli.ssc.access-control.update-local-user.usage.header = Update a local SSC user.
277+
fcli.ssc.access-control.update-local-user.email = New email for the user.
278+
fcli.ssc.access-control.update-local-user.firstname = New first name for the user.
279+
fcli.ssc.access-control.update-local-user.lastname = New last name for the user.
280+
fcli.ssc.access-control.update-local-user.password = New password for the user.
281+
fcli.ssc.access-control.update-local-user.password-never-expires = Set whether the password should never expire.
282+
fcli.ssc.access-control.update-local-user.require-password-change = Set whether a password change is required.
283+
fcli.ssc.access-control.update-local-user.suspend = Set whether the user should be suspended.
284+
fcli.ssc.access-control.update-local-user.roles = Comma-separated list of role names or ids to assign, replacing all current roles.
285+
fcli.ssc.access-control.update-local-user.add-roles = Comma-separated list of role names or ids to add to the user's current roles.
286+
fcli.ssc.access-control.update-local-user.rm-roles = Comma-separated list of role names or ids to remove from the user's current roles.
276287
fcli.ssc.access-control.delete-user.usage.header = Delete a user.
277288
fcli.ssc.access-control.get-user.usage.header = Get user details.
278289
fcli.ssc.access-control.list-users.usage.header = List users.

0 commit comments

Comments
 (0)