diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCAccessControlCommands.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCAccessControlCommands.java index 91a49c17a3..a5ee56d595 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCAccessControlCommands.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCAccessControlCommands.java @@ -32,6 +32,7 @@ SSCPermissionGetCommand.class, SSCPermissionListCommand.class, SSCUserCreateLocalCommand.class, + SSCUserUpdateLocalCommand.class, SSCUserDeleteCommand.class, SSCUserGetCommand.class, SSCUserListCommand.class, diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCUserUpdateLocalCommand.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCUserUpdateLocalCommand.java new file mode 100644 index 0000000000..f945f415d2 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/access_control/cli/cmd/SSCUserUpdateLocalCommand.java @@ -0,0 +1,157 @@ +/* + * Copyright 2021-2026 Open Text. + * + * The only warranties for products and services of Open Text + * and its affiliates and licensors ("Open Text") are as may + * be set forth in the express warranty statements accompanying + * such products and services. Nothing herein should be construed + * as constituting an additional warranty. Open Text shall not be + * liable for technical or editorial errors or omissions contained + * herein. The information contained herein is subject to change + * without notice. + */ +package com.fortify.cli.ssc.access_control.cli.cmd; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fortify.cli.common.cli.util.CommandGroup; +import com.fortify.cli.common.exception.FcliSimpleException; +import com.fortify.cli.common.output.cli.mixin.OutputHelperMixins; +import com.fortify.cli.common.output.transform.IActionCommandResultSupplier; +import com.fortify.cli.ssc._common.output.cli.cmd.AbstractSSCJsonNodeOutputCommand; +import com.fortify.cli.ssc._common.rest.ssc.SSCUrls; +import com.fortify.cli.ssc.access_control.cli.mixin.SSCUserResolverMixin; + +import kong.unirest.UnirestInstance; +import lombok.Getter; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +@Command(name = "update-local-user") @CommandGroup("user") +public class SSCUserUpdateLocalCommand extends AbstractSSCJsonNodeOutputCommand implements IActionCommandResultSupplier { + @Getter @Mixin private OutputHelperMixins.DetailsNoQuery outputHelper; + + @Mixin private SSCUserResolverMixin.PositionalParameterSingle userResolver; + + @Option(names = {"--firstname"}) + private String firstName; + @Option(names = {"--lastname"}) + private String lastName; + @Option(names = {"--email"}) + private String email; + @Option(names = {"--password"}) + private String password; + @Option(names = {"--password-never-expires", "--pne"}) + private Boolean pwNeverExpires; + @Option(names = {"--require-password-change", "--rpc"}) + private Boolean requirePwChange; + @Option(names = {"--suspend"}) + private Boolean suspend; + @Option(names = {"--roles"}, split = ",") + private ArrayList roles; + @Option(names = {"--add-roles"}, split = ",") + private ArrayList addRoles; + @Option(names = {"--rm-roles"}, split = ",") + private ArrayList rmRoles; + + @Override + public JsonNode getJsonNode(UnirestInstance unirest) { + String userId = resolveUserId(unirest); + ObjectNode userData = getLocalUser(unirest, userId); + setUserAttributes(userData); + setUserRoles(userData); + return unirest.put(SSCUrls.LOCAL_USER(userId)) + .body(userData) + .asObject(JsonNode.class).getBody(); + } + + private String resolveUserId(UnirestInstance unirest) { + ArrayNode authEntities = (ArrayNode) userResolver.getAuthEntityJsonNode(unirest); + if (authEntities.size() != 1) { + throw new FcliSimpleException( + "User specification matched %d users; please provide a unique user id", authEntities.size()); + } + return authEntities.get(0).get("id").asText(); + } + + private ObjectNode getLocalUser(UnirestInstance unirest, String userId) { + return (ObjectNode) unirest.get(SSCUrls.LOCAL_USER(userId)) + .asObject(JsonNode.class).getBody().get("data"); + } + + private void setUserAttributes(ObjectNode userData) { + if (firstName != null) { userData.put("firstName", firstName); } + if (lastName != null) { userData.put("lastName", lastName); } + if (email != null) { userData.put("email", email); } + if (password != null) { userData.put("clearPassword", password); } + if (pwNeverExpires != null) { userData.put("passwordNeverExpires", pwNeverExpires); } + if (requirePwChange != null) { userData.put("requirePasswordChange", requirePwChange); } + if (suspend != null) { userData.put("suspended", suspend); } + } + + private void setUserRoles(ObjectNode userData) { + if (roles != null) { + ArrayNode rolesArray = userData.putArray("roles"); + for (String roleId : roles) { + rolesArray.addObject().put("id", roleId); + } + } + if (addRoles != null) { + addRolesToUser(userData); + } + if (rmRoles != null) { + removeRolesFromUser(userData); + } + } + + private void addRolesToUser(ObjectNode userData) { + ArrayNode existingRoles = getOrCreateRolesArray(userData); + Set existingRoleIds = collectRoleIds(existingRoles); + for (String roleId : addRoles) { + if (!existingRoleIds.contains(roleId)) { + existingRoles.addObject().put("id", roleId); + } + } + } + + private void removeRolesFromUser(ObjectNode userData) { + ArrayNode existingRoles = (ArrayNode) userData.get("roles"); + if (existingRoles == null) { return; } + Set idsToRemove = new HashSet<>(rmRoles); + ArrayNode filteredRoles = userData.putArray("roles"); + for (JsonNode role : existingRoles) { + if (!idsToRemove.contains(role.get("id").asText())) { + filteredRoles.add(role); + } + } + } + + private ArrayNode getOrCreateRolesArray(ObjectNode userData) { + ArrayNode existing = (ArrayNode) userData.get("roles"); + return existing != null ? existing : userData.putArray("roles"); + } + + private Set collectRoleIds(ArrayNode rolesArray) { + Set ids = new HashSet<>(); + for (JsonNode role : rolesArray) { + ids.add(role.get("id").asText()); + } + return ids; + } + + @Override + public String getActionCommandResult() { + return "UPDATED"; + } + + @Override + public boolean isSingular() { + return true; + } +} diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties index dd5f7c5507..0ebf203b30 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties @@ -273,6 +273,17 @@ fcli.ssc.access-control.create-local-user.password-never-expires = Causes the pa fcli.ssc.access-control.create-local-user.require-password-change = Requires the password of the user to be changed fcli.ssc.access-control.create-local-user.suspend = Causes the user to automatically be su fcli.ssc.access-control.create-local-user.username = The username of the user +fcli.ssc.access-control.update-local-user.usage.header = Update a local SSC user. +fcli.ssc.access-control.update-local-user.email = New email for the user. +fcli.ssc.access-control.update-local-user.firstname = New first name for the user. +fcli.ssc.access-control.update-local-user.lastname = New last name for the user. +fcli.ssc.access-control.update-local-user.password = New password for the user. +fcli.ssc.access-control.update-local-user.password-never-expires = Set whether the password should never expire. +fcli.ssc.access-control.update-local-user.require-password-change = Set whether a password change is required. +fcli.ssc.access-control.update-local-user.suspend = Set whether the user should be suspended. +fcli.ssc.access-control.update-local-user.roles = Comma-separated list of role names or ids to assign, replacing all current roles. +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. +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. fcli.ssc.access-control.delete-user.usage.header = Delete a user. fcli.ssc.access-control.get-user.usage.header = Get user details. fcli.ssc.access-control.list-users.usage.header = List users.