From 17d8a21ad47294f90167c3fd1b6138cbaa7938b9 Mon Sep 17 00:00:00 2001 From: Sagar Choudhary Date: Tue, 9 Jul 2024 15:00:37 -0500 Subject: [PATCH] Add files via upload --- as400/src/AS400/pom.xml | 68 + .../connectorms/as400/AS400Connection.java | 213 ++ .../connectorms/as400/AS400Connector.java | 2169 +++++++++++++++++ .../saviynt/connectorms/as400/AS400Utils.java | 599 +++++ .../com/saviynt/connectorms/as400/Utils.java | 108 + .../exceptions/ConfigurationException.java | 22 + .../connectorms/as400/util/Constants.java | 105 + .../as400/util/PasswordGenerator.java | 69 + .../as400/util/PasswordPolicy.java | 135 + .../as400/util/RandomPasswordGenerator.java | 253 ++ 10 files changed, 3741 insertions(+) create mode 100644 as400/src/AS400/pom.xml create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connection.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connector.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Utils.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/Utils.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/exceptions/ConfigurationException.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/Constants.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordGenerator.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordPolicy.java create mode 100644 as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/RandomPasswordGenerator.java diff --git a/as400/src/AS400/pom.xml b/as400/src/AS400/pom.xml new file mode 100644 index 0000000..7ee0ea9 --- /dev/null +++ b/as400/src/AS400/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + com.saviynt.connectorms + AS400 + 1.0 + jar + + + com.saviynt.ssm + abstractConnector + 8.11.1 + provided + + + net.sf.jt400 + jt400 + 20.0.6 + java11 + + + org.slf4j + slf4j-log4j12 + 1.7.20 + provided + + + saviynt.com + idw + 4.0.0 + provided + + + + UTF-8 + 11 + 11 + com.saviynt.connectorms.as400.Utils + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + package + + single + + + + + + com.saviynt.connectorms.as400.Utils + + + + + jar-with-dependencies + + + + + + + + \ No newline at end of file diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connection.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connection.java new file mode 100644 index 0000000..5f43960 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connection.java @@ -0,0 +1,213 @@ +package com.saviynt.connectorms.as400; + +import com.ibm.as400.access.AS400; +import com.ibm.as400.access.AS400Message; +import com.ibm.as400.access.AS400SecurityException; +import com.ibm.as400.access.CommandCall; +import com.ibm.as400.access.ErrorCompletingRequestException; +import com.ibm.as400.access.SecureAS400; +import java.beans.PropertyVetoException; +import java.io.IOException; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import com.ibm.as400.access.ObjectDoesNotExistException; +import com.ibm.as400.access.RequestNotSupportedException; +import com.ibm.as400.access.SystemValue; +import com.saviynt.ssm.abstractConnector.exceptions.ConnectorException; +import java.util.Arrays; +import org.slf4j.LoggerFactory; + +/** + * + * @author marcozhang + */ +public class AS400Connection implements AutoCloseable { + + //private static final Logger log = Logger.getLogger(AS400Connection.class.getName()); + private final Logger LOGGER = (Logger) LoggerFactory.getLogger(AS400Connection.class.getName()); + private AS400 conn = null; + + /** + * The password level (QPWDLVL) of the system. + */ + public static final int QPWDLVL_UNFETCHED = -2; + public static final int QPWDLVL_UNSET = -1; + protected int passwordLevel = QPWDLVL_UNFETCHED; + + private final String hostname; + private final String username; + private final String password; + private Boolean useSSL = true; + + public AS400Connection(String host, String acct, String passwd) { + hostname = host; + username = acct; + password = passwd; + try { + newConnection(hostname, username, password, useSSL); + } catch (Exception ex) { + LOGGER.error(ex.getMessage()); + } + } + + public AS400Connection(String host, String acct, String passwd, Boolean ssl) { + hostname = host; + username = acct; + password = passwd; + useSSL = ssl; + try { + newConnection(hostname, username, password, useSSL); + } catch (Exception ex) { + LOGGER.error(ex.getMessage()); + } + } + + /** + * + */ + private void newConnection(String host, String acct, String passwd, Boolean ssl) { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Establishing connection to AS400 system: " + host); + try { + if (ssl) { + LOGGER.info("Connection with SSL"); + conn = new SecureAS400(host, acct, str2Char(passwd)); + } else { + LOGGER.info("Connection without SSL"); + conn = new AS400(host, acct, str2Char(passwd)); + } + //LOGGER.info("Connected to " + conn.getSystemName()); + } catch (Exception ex) { + //LOGGER.error("Connection error: " + ex.getMessage()); + throw new ConnectorException(ex.getMessage()); + } + + try { + // Turn off user IDs and passwords prompt + conn.setGuiAvailable(false); + authenticate(); + } catch (PropertyVetoException ex) { + //log.log(Level.SEVERE, "setGuiAvailable error: {0}", e.getMessage()); + LOGGER.error("setGuiAvailable error: " + ex.getMessage()); + throw new ConnectorException(ex.getMessage()); + } catch (AS400SecurityException | IOException ex) { + LOGGER.error("Authentiation error: " + ex.getMessage()); + throw new ConnectorException(ex.getMessage()); + } + + } + + public Boolean authenticate() throws AS400SecurityException, IOException { + boolean authenticated = false; + if (conn != null) { + authenticated = conn.authenticate(username, str2Char(password)); + if (!authenticated) { + //log.log(Level.SEVERE, "Username or password invalid!"); + LOGGER.error("Username or password invalid!"); + throw new ConnectorException("Username or password invalid!"); + } + LOGGER.info("Authentication successful"); + } + return authenticated; + } + + protected void determinePasswordLevel() { + if (conn != null) { + try { + SystemValue sysval = new SystemValue(conn, "QPWDLVL"); + Integer qpwdlvl = (Integer) sysval.getValue(); + if (qpwdlvl != null) { + passwordLevel = qpwdlvl; + } + } catch (RequestNotSupportedException e) { + //This means the resource is earlier than AS400 5.1, so QPWDLVL does not exist + //In this case, we will set password level to be -1 (QPWDLVL_UNSET) + passwordLevel = QPWDLVL_UNSET; + } catch (AS400SecurityException | ErrorCompletingRequestException | ObjectDoesNotExistException | IOException | InterruptedException e) { + LOGGER.error("Unable to determine password level" + e); + } + LOGGER.info("Password level is " + passwordLevel); + } + } + + /** + * Runs a generated command on the AS400 system. + * + * @param command Command to run on the AS400 system + * @return Returns true if successful false otherwise. + */ + public boolean runCommand(String command) { + LOGGER.setLevel(Level.ALL); + boolean success = false; + try { + //LOGGER.debug("Execute command: " + command); + LOGGER.debug("Execute command: " + AS400Utils.maskPassword(command)); + CommandCall cc = new CommandCall(conn); + //cc.setCommand(command); + success = cc.run(command); + AS400Message[] msgs = cc.getMessageList(); + if (!success) { + for (AS400Message msg : msgs) { + // Handle user profile deletion + if (command.startsWith("DLTUSRPRF") && msg.getText().endsWith("not found.")) { + success = true; + break; + } + } + for (AS400Message msg : msgs) { + LOGGER.error("Command failed message: " + msg.getText()); + } + throw new ConnectorException(Arrays.toString(msgs)); + } else { + for (AS400Message msg : msgs) { + LOGGER.debug("Command success message: " + msg.getText()); + } + } + } catch (AS400SecurityException | ErrorCompletingRequestException | IOException | InterruptedException | PropertyVetoException ex) { + LOGGER.error("Error in exeucting command: " + ex); + throw new ConnectorException(ex); + } + + return success; + } + + /** + * Tests the connection to the AS400 system. + * + * @return True if its still connected else false. + */ + public boolean isConnected() { + return (conn != null); + } + + /** + * Returns the internal connection object. + * + * @return Return the internal connection object. + */ + public AS400 getConnection() { + return conn; + } + + /** + * Closes the connection to the AS400 system. + */ + @Override + public void close() { + if (conn != null) { + conn.disconnectAllServices(); + conn = null; + } + } + + private char[] str2Char(String str) { + if (str == null) { + str = ""; + } + char[] ch = new char[str.length()]; + for (int i = 0; i < str.length(); i++) { + ch[i] = str.charAt(i); + } + return ch; + } +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connector.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connector.java new file mode 100644 index 0000000..5d0d823 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Connector.java @@ -0,0 +1,2169 @@ +package com.saviynt.connectorms.as400; + +import com.saviynt.ssm.abstractConnector.BaseConnectorSpecification; +import com.saviynt.ssm.abstractConnector.ConfigDataVo; +import com.saviynt.ssm.abstractConnector.RepositoryReconService; +import com.saviynt.ssm.abstractConnector.SearchableObject; +import com.saviynt.ssm.abstractConnector.exceptions.ConnectorException; +import com.saviynt.ssm.abstractConnector.exceptions.InvalidAttributeValueException; +import com.saviynt.ssm.abstractConnector.exceptions.InvalidCredentialException; +import com.saviynt.ssm.abstractConnector.exceptions.MissingKeyException; +import com.saviynt.ssm.abstractConnector.exceptions.OperationTimeoutException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.json.JSONObject; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.as400.access.AS400SecurityException; +import com.ibm.as400.access.ErrorCompletingRequestException; +import com.ibm.as400.access.ObjectDoesNotExistException; +import com.ibm.as400.access.RequestNotSupportedException; +import com.ibm.as400.access.User; +import com.ibm.as400.access.UserList; +import com.saviynt.ssm.abstractConnector.utility.GroovyService; +import java.io.IOException; +import java.util.Arrays; +import java.util.Enumeration; +//import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.saviynt.connectorms.as400.util.Constants; +import java.beans.PropertyVetoException; + +/** + * + * @author marcozhang + */ +public class AS400Connector extends BaseConnectorSpecification { + + /** + * The Constant serialVersionUID. + */ + private static final long serialVersionUID = 1L; + + /** + * Logger object + */ + //private static final Logger log = Logger.getLogger(AS400Connector.class.getName()); + private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(AS400Connector.class.getName()); + + public static void main(String[] args) { + } + + /** + * to retrieve connector display name. + * + * @return the string + */ + @Override + public String displayName() { + + return "AS400Connector (SCF)"; + } + + /** + * to retrieve connector version. + * + * @return the string + */ + @Override + public String version() { + + return "1.0"; + } + + /** + * to test the connection Example : To test the connection , refer to the + * below steps step 1 : retrieve connection attributes from configData/Data + * step 2 : connect to target system using JDBC connection step 3 : return + * true in Map as given below if connection is successful. Map response = + * new HashMap(); response.put("status", true); + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the connectivity status in the Map. + * @throws ConnectorException the connector exception + * @throws InvalidCredentialException the invalid credential exception + * @throws InvalidAttributeValueException the invalid attribute value + * exception + * @throws OperationTimeoutException the operation timeout exception + * @throws MissingKeyException the missing key exception + */ + @Override + public Map test(Map configData, Map data) throws ConnectorException, + InvalidCredentialException, InvalidAttributeValueException, OperationTimeoutException, MissingKeyException { + //logic to test connection + //connect to target system using configData + //write your logic + //return true or false + LOGGER.setLevel(Level.ALL); + + AS400Connection conn = null; + Map response = new HashMap(); + boolean success = false; + + try { + // Initiate AS400 connection using the configuration data from configData object + conn = connectToAS400(configData); + if (conn != null && conn.isConnected()) { + success = true; + // Log AS400 version number + LOGGER.info("AS400 Version: " + conn.getConnection().getVersion() + "." + conn.getConnection().getRelease()); + // Log password level + conn.determinePasswordLevel(); + } + // Return connection test result + response.put("status", success); //true If connection to target is successful otherwise false. + } catch (AS400SecurityException | IOException ex) { + LOGGER.error(ex.getMessage()); + throw new ConnectorException(ex.getMessage()); + } catch (ConnectorException ex) { + LOGGER.error(ex.getMessage()); + } finally { + // Release connection object + if (conn != null) { + conn.close(); + } + } + + return response; + } + + /** + * to set the config with attributes needed for creating a connection to the + * target system from SSM. The attributes defined in configData in setConfig + * are the attributes that would dynamically populate on the connection + * creation UI under SSM to be input. Connection attributes can be added in + * following way: Example: List connectionAttributes = + * configData.getConnectionAttributes(); + * connectionAttributes.add("drivername"); + * + * Connection attributes that need to be encrypted can be added to + * configData as below: Example : List encryptedConnectionAttributes + * = configData.getEncryptedConnectionAttributes(); + * encryptedConnectionAttributes.add("Password"); + * + * Description or details of the format in which the config attributes are + * supposed to be inputted from the UI can be added in configData as below: + * JSONObject jsonObject = new JSONObject(connectionAttributesDescription); + * jsonObject.put("Password", "Provide password to connect with + * application"); jsonObject.put("CreateUserJSON", "SAMPLE JSON {}"); + * configData.getConnectionAttributesDescription().setConnectionAttributesDescription(jsonObject.toString()); + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + */ + @Override + public void setConfig(ConfigDataVo configData) { + List connectionAttributes = configData.getConnectionAttributes(); + + // Add the config attributes like below: + connectionAttributes.add(Constants.HOSTNAME); + connectionAttributes.add(Constants.USERNAME); + connectionAttributes.add(Constants.PASSWORD); + connectionAttributes.add(Constants.USESSL); + connectionAttributes.add(Constants.ACCOUNT_ATTRIBUTES_JSON); + connectionAttributes.add(Constants.GROUP_ATTRIBUTES_JSON); + connectionAttributes.add(Constants.CREATEACCOUNT_JSON); + connectionAttributes.add(Constants.UPDATEACCOUNT_JSON); + connectionAttributes.add(Constants.REMOVEACCOUNT_JSON); + connectionAttributes.add(Constants.ENABLEACCOUNT_JSON); + connectionAttributes.add(Constants.DISABLEACCOUNT_JSON); + connectionAttributes.add(Constants.ADDACCESS_JSON); + connectionAttributes.add(Constants.REMOVEACCESS_JSON); + connectionAttributes.add(Constants.CHANGECREDENTIAL_JSON); + connectionAttributes.add(Constants.DYNAMICATTRIBUTES_JSON); + connectionAttributes.add(Constants.SETRANDOMPASSWORD); + connectionAttributes.add(Constants.PASSWORDPOLICY_JSON); + + //Set the attributes that need to be encrypted + encryptedConnectionAttributes(configData); + + //Set the description of config attributes + connectionAttributesDescription(configData); + + /* + set the attributes that are required as below. In example code, making all of our connection attributes as "required" + */ + List requiredConnectionAttributes = configData.getRequiredConnectionAttributes(); + //example code + + requiredConnectionAttributes.add(Constants.HOSTNAME); + requiredConnectionAttributes.add(Constants.USERNAME); + requiredConnectionAttributes.add(Constants.PASSWORD); + requiredConnectionAttributes.add(Constants.USESSL); + } + + /** + * set ConnectionAttributes that need to be encrypted + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + */ + public void encryptedConnectionAttributes(ConfigDataVo configData) { + + List encryptedConnectionAttributes = configData.getEncryptedConnectionAttributes(); + //Add name of config attributes (defined in set configData's connectionAttributes in setConfig()) that need to be encrypted + encryptedConnectionAttributes.add(Constants.PASSWORD); + } + + /** + * set description of the config attributes to be used in UI for specifying + * their required format + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + */ + public void connectionAttributesDescription(ConfigDataVo configData) { + + String connectionAttributesDescription = configData.getConnectionAttributesDescription(); + JSONObject jsonObject = new JSONObject(connectionAttributesDescription); + + /** + * Add description of the config attributes (defined in set configData's + * connectionAttributes in setConfig()) as below.These will be shown on + * the UI corresponding to these attributes as the input values format + * required + */ + jsonObject.put(Constants.HOSTNAME, "Hostname or IP address of AS400 target system."); + jsonObject.put(Constants.USERNAME, "Administrator account name."); + jsonObject.put(Constants.PASSWORD, "Administrator account password."); + jsonObject.put(Constants.USESSL, "Indicates whether to connect to the host using SSL. The default value is true."); + + jsonObject.put(Constants.ACCOUNT_ATTRIBUTES_JSON, "AS400 user profile attribute mappings"); + jsonObject.put(Constants.GROUP_ATTRIBUTES_JSON, "AS400 group profile attribute mappings"); + jsonObject.put(Constants.CREATEACCOUNT_JSON, "Create user profile configuration"); + jsonObject.put(Constants.UPDATEACCOUNT_JSON, "Update user profile configuration"); + jsonObject.put(Constants.REMOVEACCOUNT_JSON, "Remove user profile configuration"); + jsonObject.put(Constants.ENABLEACCOUNT_JSON, "Enable user profile configuration"); + jsonObject.put(Constants.DISABLEACCOUNT_JSON, "Disable user profile configuration"); + jsonObject.put(Constants.ADDACCESS_JSON, "Access user profile to supplemental group configration"); + jsonObject.put(Constants.REMOVEACCESS_JSON, "Access supplemental group from user profile configration"); + jsonObject.put(Constants.CHANGECREDENTIAL_JSON, "Change user profile password configuration"); + jsonObject.put(Constants.SETRANDOMPASSWORD, "True or False. Whether to generate password in account creation"); + jsonObject.put(Constants.PASSWORDPOLICY_JSON, "Password policy for generating password for user profile creation"); + + configData.setConnectionAttributesDescription(jsonObject.toString()); + } + + /** + * to process reconcile for users and accounts Example : to process + * reconcile for users and accounts , refer to the below steps step 1 : + * retrieve connection attributes from configData/Data step 2 : collect the + * data(Account,Users,Entitlements) from target system step 3 : set the data + * into the format accepted by connector framework's + * RepositoryReconService.notify() sample format: + * finalData=[[{ACCOUNT.CUSTOMPROPERTY2=XXXX, ACCOUNT.CUSTOMPROPERTY1=XXXX, + * ACCOUNT.NAME=XXXX}, {ENTITLEMENT.NAME=XXXX, + * ENTITLEMENT.ENTITLEMENTTYPE=XXXX, ENTITLEMENT.ENTITLEMENT_VALUE=XXXX}, + * {ACCOUNT_ATTRIBUTES.ATTRIBUTE_VALUE=XXXX, ACCOUNT_ATTRIBUTES.NAME=XXXX, + * ACCOUNT_ATTRIBUTES.ATTRIBUTE_NAME=XXXX}, {USERS.USERNAME=XXXX}]] step 4 : + * call connector framework's RepositoryReconService.notify() with finalData + * as input param from step 3 for SSM to process recon + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @param formatterClass the formatter class - This is for future + * implementation hence ignore it for now + * @throws ConnectorException the connector exception + */ + @Override + public void reconcile(Map configData, Map data, String formatterClass) { + + LOGGER.setLevel(Level.ALL); + LOGGER.info("Importing accounts and entitlements"); + //AS400Utils.printConfigData(configData, "configData"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + //"endpointId" is obtianed from ssm and is required in RepositoryReconService.notify() below + Long endpointId = Long.valueOf(data.get("endpointId").toString()); + + //List>> finalData = processAccountReconcile(configData, data); + // Call the method to retrieve AS400 user profiles and group profiles. + // Return the required format for RepositoryReconService.notify() to write account and entitlement data into the endpoint + List>> finalData = processAccountEntitlementReconcile(configData, data); + try { + LOGGER.info("calling RepositoryReconService.notify()"); + // call notify method with finalData prepared above that contains the reconciled data + // for reconiliation of accounts in SSM. As explained in the parameter definition, formatterClass + // can be ignored for now + //LOGGER.debug("Saving accounts: " + finalData); + RepositoryReconService.notify(finalData, endpointId, null, data); + + // Release finalData as it can be big + finalData.clear(); + } catch (Exception e) { + LOGGER.error("Exception occured in importing data " + e.getMessage()); + LOGGER.error("Stack trace: " + Arrays.toString(e.getStackTrace())); + } + + } + + private List>> processAccountEntitlementReconcile(Map configData, Map data) { + /* + * Write your own logic to pull accounts + * Write your own logic to pull entitlements + * Write your own logic to pull accounts and entitlements relationship + * Build accountsAllDataMap. Below is the sample data format + [ + [ + {}, + { + ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.TYPE=Group, ENTITLEMENT.ENTITLEMENTMAPPINGJSON=, ENTITLEMENT.Description=, ENTITLEMENT.CUSTOMPROPERTY2=xxx, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + {} + ], + [ + { + ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.CUSTOMPROPERTY2=xxx, ACCOUNT.DESCRIPTION=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + }, + { + ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.TYPE=Group, ENTITLEMENT.ENTITLEMENTMAPPINGJSON={}, ENTITLEMENT.Description=xxx, ENTITLEMENT.CUSTOMPROPERTY2=xxx, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + { + ACCOUNT_ATTRIBUTES.PRINCIPALSOURCE=xxx, ACCOUNT_ATTRIBUTES.GROUPID=xxx + } + ] + ] + */ + + // List of list that is used to store entitlements, accounts and account/entitlement assoications. It is returned for + // RepositoryReconService.notify() to write back into EIC endpoint + List>> accountsAllDataMap = new ArrayList<>(); + + // A variable for storing all retrieved groups to used to verify if the supplmental groups in user profile attribute SUPGRPPRF exist + Map> group_map = new HashMap(); + + // User and group are essentially user profile in AS400. For user, GID is 0. + // Initiate AS400 connection using the credential from configData object map + AS400Connection conn = connectToAS400(configData); + if (conn.getConnection() != null) { + // Call JTOpen UserList method to create the UserList object + // https://www.ibm.com/docs/en/i/7.5?topic=classes-user-group + // https://www.ibm.com/docs/en/i/7.5?topic=ssw_ibm_i_75/rzahh/javadoc/com/ibm/as400/access/UserList.htm + UserList users = new UserList(conn.getConnection()); + + // **************************************************** + // Firstly retrieve and process all groups + // **************************************************** + try { + // Set selection value indicating that the list contains only user profiles that are group profiles. + users.setUserInfo(UserList.GROUP); + LOGGER.debug("No. of Groups: " + users.getLength()); + try { + // Get the list of all groups + Enumeration io = users.getUsers(); + // Iterate through the list + while (io.hasMoreElements()) { + // Get AS400 group profile object + User object = (User) io.nextElement(); + + /* Initate a map to store one entitlement (group) in the following format + * + * {ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.TYPE=Group, ENTITLEMENT.CUSTOMPROPERTY1=xxx} + */ + Map oneEnt = new HashMap<>(); + + /* GROUP_ATTRIBUTES defines how AS400 group attributes are mapped to EIC entitlement properties + * Sample GROUP_ATTRIBUTES format looks like below + * propLabels section is not used at the moment. It is only there as a reference for labeling purpose + { + "propLabels": { + "customproperty1": "User class" + }, + "colsToPropsMap": { + "ENTITLEMENTID": "USRPRF", + "ENTITLEMENT_VALUE": "USRPRF", + "customproperty1": "USRCLS" + } + } + * Convert colsToPropsMap section of GROUP_ATTRIBUTES json configuration into a map for use later + */ + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.GROUP_ATTRIBUTES_JSON); + + // Iterate through defined group properties and get the value of each AS400 group attribute + for (Map.Entry group : attrs_map.entrySet()) { + // This is the EIC entitlement property name + String savPropName = group.getKey(); + // This is the AS400 group profile attribute name + String as400ParamName = (String) group.getValue(); + // Get the actual value of AS400 group profile attribute + Object asValue = AS400Utils.getParamValue(object, as400ParamName); + // Save converted entry into entitlement map in oneEnt variable + // it will look like: + // ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.CUSTOMPROPERTY1=xxx + if (asValue != null && !asValue.toString().isEmpty()) { + oneEnt.put(Constants.ENTITLEMENT_PREFIX + savPropName, asValue); + } + } + + // ENTITLEMENTID, ENTITLEMENT_VALUE, TYPE are minimum properties required. If they are not defined in GROUP_ATTRIBUTES add them here + // If "TYPE": "Group" is not defined in GROUP_ATTRIBUTES json configuration, add it here + if (oneEnt.get(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENTTYPE) == null) { + oneEnt.put(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENTTYPE, Constants.ENTITLEMENT_TYPE_GROUP); + } + + // If "ENTITLEMENTID": "USRPRF" is not defined in GROUP_ATTRIBUTES json configuration, add it here + if (oneEnt.get(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENTID) == null) { + oneEnt.put(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENTID, object.getName()); + } + // If "ENTITLEMENT_VALUE": "USRPRF" is not defined in GROUP_ATTRIBUTES json configuration, add it here + if (oneEnt.get(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENT_VALUE) == null) { + oneEnt.put(Constants.ENTITLEMENT_PREFIX + Constants.SAV_ENTATR_ENTITLEMENT_VALUE, object.getName()); + } + + // Store entitlement detail for later use + // This shouldn't be required if the service account permission is properly given. + // In a situation when the service account doesn't have enough permissions to retrieve all the groups, we don't want to + // return those groups appear in user profile attribute SUPGRPPRF but not retrieved by getUser() call to appear in EIC as group entitlement + LOGGER.debug("Add group: " + object.getName()); + group_map.put(object.getName(), oneEnt); + + /* Construct one complete entitlement (group) entry in the format like below + * the first and last empty element are intentional though they aren't needed in fact as this is entitlement not account + * As stated in documentation, each inner array includes three maps: accounts data, associated entitlement data, and additional account attribute data in the following format: + [{Account1}, {Ent1}, {ACCOUNT_ATTRIBUTES}] + * https://docs.saviyntcloud.com/bundle/SCF_v24x/page/Content/Developing-the-Connector.htm + * Sample data: + [ + {}, + { + ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.TYPE=Group, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + {} + ] + */ + List> entSet = new ArrayList<>(); + entSet.add(new HashMap<>()); + entSet.add(oneEnt); + entSet.add(new HashMap<>()); + // Finally, append entitlement details to the list + //LOGGER.info("Entitlement data: " + entSet); + accountsAllDataMap.add(entSet); + } + + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException | RequestNotSupportedException ex) { + LOGGER.error("Error iterating groups {}", ex.getMessage()); + } + } catch (PropertyVetoException ex) { + LOGGER.error("setUserInfo error {}", ex.getMessage()); + } + + // **************************************************** + // Secondly retrieve and process all users + // **************************************************** + try { + // Set selection value indicating that the list contains only user profiles that are not group profiles. + users.setUserInfo(UserList.USER); + LOGGER.debug("No. of Users: " + users.getLength()); + try { + // Get the list of all users + Enumeration io = users.getUsers(); + // Iterate through the list + while (io.hasMoreElements()) { + // Get AS400 user profile object + User object = (User) io.nextElement(); + + /* Initate a map to store one user (account) in the following format + * {ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID} + */ + Map oneAccount = new HashMap<>(); + + /* ACCOUNT_ATTRIBUTES defines how AS400 user attributes are mapped to EIC account properties + * Sample ACCOUNT_ATTRIBUTES format looks like below + * propLabels section is not used at the moment. It is only there as a reference for labeling purpose + { + "propLabels": { + "customproperty1": "User class (CP1)", + "customproperty2": "Assistance level (CP2)" + }, + "colsToPropsMap": { + "RECONCILIATION_FIELD": "ACCOUNTID", + "accountID": "USRPRF", + "name": "USRPRF", + "customproperty1": "USRCLS" + } + } + * Convert colsToPropsMap section of ACCOUNT_ATTRIBUTES json configuration into a map for use later + */ + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.ACCOUNT_ATTRIBUTES_JSON); + + // Iterate through defined account properties and get the value of each AS400 user attribute + for (Map.Entry attribute : attrs_map.entrySet()) { + // This is the EIC acccount property name + String savPropName = attribute.getKey(); + // This is the AS400 user profile attribute name + String as400ParamName = (String) attribute.getValue(); + // Get the actual value of AS400 user profile attribute + Object asValue = AS400Utils.getParamValue(object, as400ParamName); + // Save converted entry into account map in oneAccount variable + // it will look like: + // ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + if (asValue != null && !asValue.toString().isEmpty()) { + oneAccount.put(Constants.ACCOUNT_PREFIX + savPropName, asValue); + } + } + + // ACCOUNTID, NAME, RECONCILIATION_FIELD are minimum properties required. If they are not defined in ACCOUNT_ATTRIBUTES add them here + // If "RECONCILIATION_FIELD": "ACCOUNTID" is not defined in ACCOUNT_ATTRIBUTES json configuration, add it here + if (oneAccount.get(Constants.ACCOUNT_PREFIX + Constants.OPT_RECONCILIATION_FIELD) == null) { + oneAccount.put(Constants.ACCOUNT_PREFIX + Constants.OPT_RECONCILIATION_FIELD, Constants.OPT_DEFAULT_RECONCILIATION_FIELD); + } + + // If "accountID": "USRPRF" is not defined in ACCOUNT_ATTRIBUTES json configuration, add it here + if (oneAccount.get(Constants.ACCOUNT_PREFIX + Constants.SAV_ACCTATTR_ACCOUNTID) == null) { + oneAccount.put(Constants.ACCOUNT_PREFIX + Constants.SAV_ACCTATTR_ACCOUNTID, object.getName()); + } + + // If "name": "USRPRF" is not defined in ACCOUNT_ATTRIBUTES json configuration, add it here + if (oneAccount.get(Constants.ACCOUNT_PREFIX + Constants.SAV_ACCTATTR_NAME) == null) { + oneAccount.put(Constants.ACCOUNT_PREFIX + Constants.SAV_ACCTATTR_NAME, object.getName()); + } + + /* Construct one complete account entry in the format like below + * As stated in documentation, each inner array includes three maps: accounts data, associated entitlement data, and additional account attribute data in the following format: + [{Account1}, {Ent1}, {ACCOUNT_ATTRIBUTES}] + * https://docs.saviyntcloud.com/bundle/SCF_v24x/page/Content/Developing-the-Connector.htm + * Sample data: + [ + { + ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + }, + { + ENTITLEMENT.ENTITLEMENTID=xxx, ENTITLEMENT.ENTITLEMENT_VALUE=xxx, ENTITLEMENT.TYPE=Group, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + { + } + ] + * We will not handle ACCOUNT_ATTRIBUTES in this implementation + */ + LOGGER.debug("Add user: " + object.getName()); + // Get list of supplemental groups + String[] groupList = object.getSupplementalGroups(); + List groups = new ArrayList(); + if (groupList != null) { + groups = Arrays.asList(groupList); + } + // Add associated entitlements into the account set + if (groups.isEmpty()) { + // If no supplemental group for the user, simply add the following into final return variable + /* + [ + { + ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + }, + {}, + {} + ] + */ + LOGGER.debug("No groups for user " + object.getName()); + List> accountSet = new ArrayList<>(); + // Add account details + accountSet.add(oneAccount); + // Add empty group details + accountSet.add(new HashMap<>()); + // Add empty account attribute details + accountSet.add(new HashMap<>()); + // Append account details to the list + //LOGGER.info("Account data: " + accountSet); + accountsAllDataMap.add(accountSet); + } else { + // If there are supplemental groups, add them one by one. + // If there are 2 supplemental groups, it should add 2 list elements like below + /* + [ + { + ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=account1, ACCOUNT.ACCOUNTID=account1, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + }, + { + ENTITLEMENT.ENTITLEMENTID=group1, ENTITLEMENT.ENTITLEMENT_VALUE=group1, ENTITLEMENT.TYPE=Group, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + { + } + ], + [ + { + ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=account1, ACCOUNT.ACCOUNTID=account1, ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID + }, + { + ENTITLEMENT.ENTITLEMENTID=group2, ENTITLEMENT.ENTITLEMENT_VALUE=group2, ENTITLEMENT.TYPE=Group, ENTITLEMENT.CUSTOMPROPERTY1=xxx + }, + { + } + ] + */ + LOGGER.debug("Assoicate groups: " + groups); + Boolean no_real_group = true; + // if the account belongs to groups, add group assoication + for (String group : groups) { + // this is used to store account and optionally one associated entitlement + List> accountSet = new ArrayList<>(); + Map real_group = group_map.get(group); + // Check if the group from SUPGRPPRF was actually retrieved previously. Only add it if it exist + // There seems to be the case whereby the group is in supplemental group attribute but it actually is not retreived due to permission issue + if (real_group != null) { + no_real_group = false; + // Add account details + accountSet.add(oneAccount); + // Add group details + accountSet.add(group_map.get(group)); + // Add empty account attribute details + accountSet.add(new HashMap<>()); + // Append account details to the list + //LOGGER.info("Account data: " + accountSet); + accountsAllDataMap.add(accountSet); + } else { + LOGGER.info("Missing group {} for user {}", group, object.getName()); + } + } + // If none of the groups in supplemental goup attribute actually exist, simply add the account info + if (no_real_group) { + List> accountSet = new ArrayList<>(); + accountSet.add(oneAccount); + accountsAllDataMap.add(accountSet); + accountSet.add(new HashMap<>()); + accountSet.add(new HashMap<>()); + } + } + + } + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException | RequestNotSupportedException ex) { + LOGGER.error("Error iterating users {}", ex.getMessage()); + } + } catch (PropertyVetoException ex) { + LOGGER.error("setUserInfo error {}", ex.getMessage()); + } + + } + + //LOGGER.debug("final return data: {}", accountsAllDataMap); + return accountsAllDataMap; + } + + /**/ + private Map porcessConfigJSON(Map configData, Map data, String configName) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Processing " + configName); + + // Get json configuration + Object config_obj = data.get(configName); + String resolved_json = null; + if (config_obj != null) { + try { + // When dynamic attributes are defined in the endpoint and the dynamic attributes are used in connection json attribute, + // they will appear in data map object if the provisioning task is created from request but not from technical rule. + // So, add them into data map object before the process of replacing binding variables otherwise error will be thrown if + // provisioning task is created from technical rule. + Map newdata = AS400Utils.addDynamicAttributes(configData, data); + + // if it is create account, add random password into data object + if (configName.equals(Constants.CREATEACCOUNT_JSON)) { + String setPwd = (String) configData.get(Constants.SETRANDOMPASSWORD); + + // If provisioning task is created from technical rule, randomPassword binding isn't populated in data map object. + // So, generate random password with the following customised feature. However, this is a problem in this approach + // because the generated password cannot be retrieved in email template. + if (setPwd != null && setPwd.toUpperCase().equals("TRUE")) { + String pwd = AS400Utils.generatePassword(configData); + if (pwd != null) { + newdata.put("randomPassword", pwd); + } + } + } + // Replaces the binding variables in the connection attributes + resolved_json = GroovyService.convertTemplateToString(config_obj.toString(), newdata); + } catch (Exception ex) { + LOGGER.error("Error in resolving binding variables: " + ex); + throw new ConnectorException("Error in resolving binding variables: " + ex); + } + } + + Map config_map = null; + if (resolved_json != null && !resolved_json.trim().isEmpty()) { + ObjectMapper mapper = new ObjectMapper(); + try { + config_map = mapper.readValue(resolved_json, HashMap.class); + } catch (JsonProcessingException ex) { + LOGGER.error("Error in converting json string to map: " + ex); + throw new ConnectorException("Error in converting json string to map: " + ex); + } + } + + return config_map; + } + + private AS400Connection connectToAS400(Map configData) { + // Get AS400 connectivity details + String host = (String) configData.get(Constants.HOSTNAME); + String username = (String) configData.get(Constants.USERNAME); + String password = (String) configData.get(Constants.PASSWORD); + String ssl = (String) configData.get(Constants.USESSL); + return new AS400Connection(host, username, password, (ssl.equalsIgnoreCase("TRUE"))); + } + + private Boolean sendCommand(AS400Connection conn, String command) throws ConnectorException { + boolean success = false; + try { + if (conn.getConnection() != null) { + success = conn.runCommand(command); + } + } finally { + if (conn != null) { + conn.close(); + } + } + return success; + } + + /* + * Return account creation result in the following format + * {ACCOUNT.CUSTOMPROPERTY1=xxx, ACCOUNT.NAME=xxx, ACCOUNT.ACCOUNTID=xxx, ACCOUNT.DESCRIPTION=xxx} + */ + private Map getResponseMetaData(User as400user, Map attrs_map) { + + // If accountid, name and RECONCILIATION_FIELD are missing, add them + attrs_map.put(Constants.OPT_RECONCILIATION_FIELD, + attrs_map.get(Constants.OPT_RECONCILIATION_FIELD) != null ? attrs_map.get(Constants.OPT_RECONCILIATION_FIELD) : Constants.OPT_DEFAULT_RECONCILIATION_FIELD); + attrs_map.put(Constants.SAV_ACCTATTR_ACCOUNTID, + attrs_map.get(Constants.SAV_ACCTATTR_ACCOUNTID) != null ? attrs_map.get(Constants.SAV_ACCTATTR_ACCOUNTID) : Constants.OPT_USRPRF); + attrs_map.put(Constants.SAV_ACCTATTR_NAME, + attrs_map.get(Constants.SAV_ACCTATTR_NAME) != null ? attrs_map.get(Constants.SAV_ACCTATTR_NAME) : Constants.OPT_USRPRF); + + //List>> accountsAllDataMap = new ArrayList<>(); + //List> acctEnt = new ArrayList<>(); + Map oneAccount = new HashMap<>(); + + // Add other defined attribute values + for (Map.Entry attribute : attrs_map.entrySet()) { + // This is the EIC acccount property name + String savPropName = attribute.getKey(); + // This is the AS400 user profile attribute name + String as400ParamName = (String) attribute.getValue(); + // Get the actual value of AS400 user profile attribute + Object asValue = AS400Utils.getParamValue(as400user, as400ParamName); + if (asValue != null && !asValue.toString().isEmpty()) { + oneAccount.put(Constants.ACCOUNT_PREFIX + savPropName, asValue); + } + } + + // We don't need ACCOUNT.RECONCILIATION_FIELD=ACCOUNTID so let's remove it + oneAccount.remove(Constants.ACCOUNT_PREFIX + Constants.OPT_RECONCILIATION_FIELD); + //acctEnt.add(oneAccount); + //accountsAllDataMap.add(acctEnt); + LOGGER.debug("ReconcileAccount = {}", oneAccount.toString()); + + return oneAccount; + } + + /** + * This is for future implementation hence ignore it for now to check + * existing record for the input object USER, ACCOUNT, ENTITLEMENT, + * ACCOUNT_ENTITLEMENT in SSM Example : to check existing record for the + * input object(for account) , refer to the below steps step 1 : retrieve + * connection attributes from configData/Data step 2 : set the data with + * filters if any step 3 : call getObjectList step 4 : return true if object + * exists + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @param serachableObject for serachableObject enabled for the entities + * USER, ACCOUNT, ENTITLEMENT, ACCOUNT_ENTITLEMENT + * @return the boolean true or false + * @throws ConnectorException the connector exception + */ + @SuppressWarnings("rawtypes") + @Override + public Boolean checkExisting(Map configData, Map data, + SearchableObject serachableObject) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Check account exist"); + + Boolean recordFound = false; + //Connect to target System With Config data + //Check in SSM to find user with firstname + Map userMap = new HashMap(); + userMap.put("firstname", "firstnameissaviynt"); + //call SaviyntReadOnlyObject.getObjectList(ExposedObject sObject,Map filter,Integer firstResult,Integer maxResult) with below arguments in the below order + // ExposedObject sObject - USERS in this use case + // filter is the criteria to retrieve user object which is userMap in this use case + // Integer firstResult - row number which is 1 in this use case + // Integer maxResult - maximum results + //List resultList = SaviyntReadOnlyObject.getObjectList(ExposedObject.USERS, userMap,1,2); + //Return true if resultList >0 + return recordFound; + } + + /** + * to create account in the target system Example : to create account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to create + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM (metadata + * built with help of reconcile json mapping). Metadata is nothing but set + * of properties from the target system to be updated in SSM for that + * account. Example : Match the corresponding column of SSM with target + * system data and build sampleMetadata. map sampleMetadata to + * provisioningData key in the result map as below and return + * map.put("provisioningData",sampleMetadata) metadata sample format : + * {ACCOUNT.COLUMNNAME1=XXXX, ACCOUNT.COLUMNNAME2=XXXX, + * ACCOUNT.COLUMNNAME3=XXXX} This is optional filed. If no metadata needs to + * updated in SSM, it can be set to null Example : + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map createAccount(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Creating account"); + + //com.saviynt.ssm.entity.ArsTasks task = (com.saviynt.ssm.entity.ArsTasks) data.get("arsTasks"); + //LOGGER.debug("randompassword: " + task.getPassword()); + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.CREATEACCOUNT_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.CREATEACCOUNT_JSON); + + // Form AS400 user profile creation command + String command = AS400Utils.constructCreateUserCommand(config_map); + + Map responseMap = new HashMap(); + + //AS400Connection conn = connectToAS400(configData); + // Execute creation user command + if (sendCommand(connectToAS400(configData), command)) { + // If we reach this step that means user creation is successful. Let's retrieve user information from target directly. + String accountName = data.get("accountName").toString(); + LOGGER.debug("Retrieving user name: " + accountName); + + // sendcommand method closes connection automatically, so we need to re-establish connection again + AS400Connection newconn = connectToAS400(configData); + try { + User newuser = new User(newconn.getConnection(), accountName); + + //build and return a map as explained in the @return Map description with this method + // Convert colsToPropsMap section of ACCOUNT_ATTRIBUTES json configuration into a map for use later + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.ACCOUNT_ATTRIBUTES_JSON); + + // Build return map and write it back to account object in EIC + Map resultMap = getResponseMetaData(newuser, attrs_map); + + //LOGGER.debug("Return map: {}", resultMap); + //Map which consists of metadata to be updated in SSM. Refer to the javadocs of this method for map format + responseMap.put("provisioningData", resultMap); + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error("Error retrieving user {} {}", accountName, ex.getMessage()); + throw new ConnectorException("Error retrieving user " + accountName + ". " + ex.getMessage()); + } finally { + if (newconn != null) { + newconn.close(); + } + } + + } else { + throw new ConnectorException("Failed to create account."); + } + + return responseMap; + } + + /** + * to update account in the target system Example : to update account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to update + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM (metadata + * built with help of reconcile json mapping). Metadata is nothing but set + * of properties from the target system to be updated in SSM for that + * account. Example:Match the corresponding column of SSM with target system + * data and build sampleMetadata. map sampleMetadata to provisioningData key + * in the result map as below and return + * map.put("provisioningData",sampleMetadata) metadata sample format : + * {ACCOUNT.COLUMNNAME1=XXXX, ACCOUNT.COLUMNNAME2=XXXX, + * ACCOUNT.COLUMNNAME3=XXXX} This is optional filed. If no metadata needs to + * updated in SSM, it can be set to null Example : + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map updateAccount(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Updating account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.UPDATEACCOUNT_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.UPDATEACCOUNT_JSON); + + // Form AS400 user profile update command + String command = AS400Utils.constructUpdateUserCommand(config_map); + + Map responseMap = new HashMap(); + //AS400Connection conn = connectToAS400(configData); + + // Execute update user command + if (sendCommand(connectToAS400(configData), command)) { + + // If we reach this step that means user creation is successful. Let's retrieve user information from target directly. + String accountName = data.get("accountName").toString(); + LOGGER.debug("Retrieving user name: " + accountName); + + // sendcommand method closes connection automatically, so we need to re-establish again + AS400Connection newconn = connectToAS400(configData); + try { + User newuser = new User(newconn.getConnection(), accountName); + + //build and return a map as explained in the @return Map description with this method + // Convert colsToPropsMap section of ACCOUNT_ATTRIBUTES json configuration into a map for use later + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.ACCOUNT_ATTRIBUTES_JSON); + + // Build return map and write it back to account object in EIC + Map resultMap = getResponseMetaData(newuser, attrs_map); + //LOGGER.info("Return map: {}", resultMap); + //Map which consists of metadata to be updated in SSM. Refer to the javadocs of this method for map format + responseMap.put("provisioningData", resultMap); + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error("Error retrieving user {} {}", accountName, ex.getMessage()); + throw new ConnectorException("Error retrieving user " + accountName + ". " + ex.getMessage()); + } finally { + if (newconn != null) { + newconn.close(); + } + } + } else { + throw new ConnectorException("Failed to update account."); + } + + return responseMap; + } + + /** + * to lock the account in target system Example : to lock account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to lock + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map lockAccount(Map configData, Map data) throws ConnectorException { + //Connect to target System With Config data + //lock Account in target System + Map map = new HashMap(); + //build and return a map as explained in the @return Map description with this method + //map.put("provisioningData",metadata map); + return map; + } + + /** + * to disable account in the target system Example : to disable account in + * the target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to disable + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map disableAccount(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Disabling account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.DISABLEACCOUNT_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.DISABLEACCOUNT_JSON); + + // Form disable user command + String command = AS400Utils.constructUpdateUserCommand(config_map); + + Map responseMap = new HashMap(); + // Execute disable user command + if (sendCommand(connectToAS400(configData), command)) { + //build and return a map as explained in the @return Map description with this method + responseMap.put("provisioningData", null); + } else { + throw new ConnectorException("Failed to disable account."); + } + + return responseMap; + } + + /** + * to unlock account in the target system Example : to disable account in + * the target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to disable + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map unLockAccount(Map configData, Map data) throws ConnectorException { + //Connect to target System With Config data + //unLock Account in target System + Map map = new HashMap(); + //build and return a map as explained in the @return Map description with this method + //map.put("provisioningData",metadata map); + return map; + } + + /** + * to enable account in the target system Example : to enable account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to enable + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map enableAccount(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Enabling account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.ENABLEACCOUNT_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.ENABLEACCOUNT_JSON); + + // Form enable user command + String command = AS400Utils.constructUpdateUserCommand(config_map); + + Map responseMap = new HashMap(); + // Execute enable user command + if (sendCommand(connectToAS400(configData), command)) { + //build and return a map as explained in the @return Map description with this method + responseMap.put("provisioningData", null); + } else { + throw new ConnectorException("Failed to enable account."); + } + + return responseMap; + + } + + /** + * to terminate account in the target system Example : to terminate account + * in the target system , refer to the below steps step 1 : retrieve + * connection attributes from configData/Data step 2 : connect to the target + * system step 3 : execute the query/process with the required input to + * terminate account in the target system step 4 : return the number of + * records updated + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the integer number of accounts terminated + * @throws ConnectorException the connector exception + */ + @Override + public Integer terminateAccount(Map configData, Map data) + throws ConnectorException { + // write your own logic to terminate the account in the target system + return 1; + } + + /** + * to remove account in the target system Example : to remove account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to remove + * account in the target system step 4 : return the Map with metadata as + * explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null)} + * + * @throws ConnectorException the connector exception + */ + @Override + public Map removeAccount(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Remove account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.REMOVEACCOUNT_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.REMOVEACCOUNT_JSON); + + //The is the account name of the account being processed. This is set by EIC during provisioning job invocation + String accountName = data.get("accountName").toString(); + //LOGGER.info("accountName: " + accountName); + + // Handle the case of OWNOBJOPT + String gpfName = null; + String ownObjOpt = (String) config_map.get("OWNOBJOPT"); + + /* + if ( ownObjOpt == null || ownObjOpt.toUpperCase().startsWith("CHGOWN ") || ownObjOpt.toUpperCase().startsWith("*CHGOWN ")) { + // If OWNOBJOPT isn't specified or OWNOBJOPT is specified with option CHGOWN without owner user profile name, + // Or if PGPOPT is specified with option CHGPGP without user profile name, + // we need to find user's group profile + AS400Connection conn = connectToAS400(configData); + try { + User grpUser = new User(conn.getConnection(), accountName); + gpfName = grpUser.getGroupProfileName(); + LOGGER.debug("Retrieved user: " + grpUser.getUserProfileName()); + LOGGER.debug("Retrieved user group profile: " + gpfName); + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error(ex.getMessage()); + throw new ConnectorException("Cannot retrieve user profile: " + accountName); + } finally { + if (conn != null) { + conn.close(); + } + } + }*/ + + if (ownObjOpt != null && (ownObjOpt.toUpperCase().startsWith("CHGOWN") || ownObjOpt.toUpperCase().startsWith("*CHGOWN"))) { + // If OWNOBJOPT is specified and its value is *CHGOWN, let's find the user profile that to be deleted user's objects are assigned + // First get to deleted user's group profile, if not found set to QDFTOWN instead + AS400Connection conn = connectToAS400(configData); + try { + User grpUser = new User(conn.getConnection(), accountName); + gpfName = grpUser.getGroupProfileName(); + LOGGER.debug("Retrieved user: " + grpUser.getUserProfileName()); + LOGGER.debug("Retrieved user group profile: " + gpfName); + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error(ex.getMessage()); + throw new ConnectorException("Cannot retrieve user profile: " + accountName); + } finally { + if (conn != null) { + conn.close(); + } + } + } + + // Form AS400 user profile deletion command + String command = AS400Utils.constructDeleteUserCommand(config_map, gpfName); + + Map responseMap = new HashMap(); + // Execute AS400 user deletion command + if (sendCommand(connectToAS400(configData), command)) { + //build and return a map as explained in the @return Map description with this method + responseMap.put("provisioningData", null); + } else { + throw new ConnectorException("Failed to delete account."); + } + + return responseMap; + } + + /** + * to add access to account in the target system Example : to add access to + * account in the target system , refer to the below steps step 1 : retrieve + * connection attributes from configData/Data step 2 : connect to the target + * system step 3 : execute the query/process with the required input to add + * access to account in the target system step 4 : return the Map with + * metadata as explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map addAccessToAccount(Map configData, Map data) + throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Add entitlement to account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.ADDACCESS_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.ADDACCESS_JSON); + + //The is the account name of the account being processed. This is set by EIC during provisioning job invocation + String accountName = data.get("accountName").toString(); + //LOGGER.debug("accountName: " + accountName); + + String entvalue = (String) config_map.get(Constants.SAV_JSONATTR_GROUP); + //LOGGER.debug("Entitlement to be added: " + entvalue); + + // We need to retrieve user's current supplemental groups so that we can append the new group into them + AS400Connection conn = connectToAS400(configData); + List groups_array = new ArrayList<>(); + try { + User user = new User(conn.getConnection(), accountName); + String[] groups = user.getSupplementalGroups(); + LOGGER.debug("Retrieved supplemental groups: " + Arrays.toString(groups)); + + if (groups.length > 0) { + groups_array.addAll(Arrays.asList(groups)); + } + + if (!groups_array.contains(entvalue)) { + groups_array.add(entvalue); + } + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error(ex.getMessage()); + throw new ConnectorException("Cannot retrieve user profile: " + accountName); + } + + // Form add group command + String command = AS400Utils.constructUpdateGroupCommand(config_map, groups_array); + + Map responseMap = new HashMap(); + // Reuse the previous connection object and execute add group command + if (sendCommand(conn, command)) { + // We supposed to retrieve user profile again so that we can update Supplemental groups attribute + // But update provisioningData isn't seem to be supported like createAccount method. + // So, comment out the following block and set provisioningData to null instead + + /* + //build and return a map as explained in the @return Map description with this method + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.ACCOUNT_ATTRIBUTES_JSON); + // If we reach this step that means user creation is successful. Let's retrieve user information from target directly. + LOGGER.debug("Retrieving user name: " + accountName); + + // sendcommand method closes connection automatically, so we need to re-establish again + AS400Connection newconn = connectToAS400(configData); + try { + User newuser = new User(newconn.getConnection(), accountName); + + //List>> resultMap = provisioningReturnMap(config_map, account_attributes); + Map resultMap = getResponseMetaData(newuser, attrs_map); + //LOGGER.info("Return map: {}", resultMap); + //Map which consists of metadata to be updated in SSM. Refer to the javadocs of this method for map format + responseMap.put("provisioningData", resultMap); + + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error("Error retrieving user {} {}", accountName, ex.getMessage()); + throw new ConnectorException("Error retrieving user " + accountName + ". " + ex.getMessage()); + } finally { + if (newconn != null) { + newconn.close(); + } + } + */ + //build and return a map as explained in the @return Map description with this method + responseMap.put("provisioningData", null); + } else { + throw new ConnectorException("Failed to add group to account."); + } + + return responseMap; + } + + /** + * to remove access to account in the target system Example : to remove + * access to account in the target system , refer to the below steps step 1 + * : retrieve connection attributes from configData/Data step 2 : connect to + * the target system step 3 : execute the query/process with the required + * input to remove access to account in the target system step 4 : return + * the Map with metadata as explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map removeAccessToAccount(Map configData, Map data) + throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Remove entitlement from account"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.REMOVEACCESS_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.REMOVEACCESS_JSON); + + //The is the account name of the account being processed. This is set by EIC during provisioning job invocation + String accountName = data.get("accountName").toString(); + //LOGGER.debug("accountName: " + accountName); + //EntitlementValues entObj = (EntitlementValues) data.get("entitlementValues"); + //String entvalue = entObj.getEntitlementvalue(); + String entvalue = (String) config_map.get(Constants.SAV_JSONATTR_GROUP); + //LOGGER.debug("Entitlement to be removed: " + entvalue); + + List groups_array = new ArrayList<>(); + // We need to retrieve user's current supplemental groups and remove the requested group from the list + AS400Connection conn = connectToAS400(configData); + try { + User user = new User(conn.getConnection(), accountName); + String[] groups = user.getSupplementalGroups(); + LOGGER.debug("Retrieved user groups: " + Arrays.toString(groups)); + + if (groups.length > 0) { + groups_array.addAll(Arrays.asList(groups)); + } + + if (groups_array.contains(entvalue)) { + groups_array.remove(entvalue); + } + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error(ex.getMessage()); + throw new ConnectorException("Cannot retrieve user profile: " + accountName); + } + + // Form update group command + String command = AS400Utils.constructUpdateGroupCommand(config_map, groups_array); + + Map responseMap = new HashMap(); + // Reuse the previous connection object and execute update group command + if (sendCommand(conn, command)) { + // We supposed to retrieve user profile again so that we can update Supplemental groups attribute + // But update provisioningData isn't seem to be supported like createAccount method. + // So, comment out the following block and set provisioningData to null instead + + /* + //build and return a map as explained in the @return Map description with this method + Map attrs_map = AS400Utils.getAttributeMappings(configData, Constants.ACCOUNT_ATTRIBUTES_JSON); + // If we reach this step that means user creation is successful. Let's retrieve user information from target directly. + LOGGER.debug("Retrieving user name: " + accountName); + + // sendcommand method closes connection automatically, so we need to re-establish again + AS400Connection newconn = connectToAS400(configData); + try { + User newuser = new User(newconn.getConnection(), accountName); + + //List>> resultMap = provisioningReturnMap(config_map, account_attributes); + Map resultMap = getResponseMetaData(newuser, attrs_map); + //LOGGER.info("Return map: {}", resultMap); + //Map which consists of metadata to be updated in SSM. Refer to the javadocs of this method for map format + responseMap.put("provisioningData", resultMap); + } catch (AS400SecurityException | ErrorCompletingRequestException | InterruptedException | IOException | ObjectDoesNotExistException ex) { + LOGGER.error("Error retrieving user {} {}", accountName, ex.getMessage()); + throw new ConnectorException("Error retrieving user " + accountName + ". " + ex.getMessage()); + } finally { + if (newconn != null) { + newconn.close(); + } + } + */ + //build and return a map as explained in the @return Map description with this method + responseMap.put("provisioningData", null); + } else { + throw new ConnectorException("Failed to remove group from account."); + } + + return responseMap; + } + + /** + * to change password in the target system Example : to change password in + * the target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to change + * password in the target system step 4 : return true if successful + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the boolean true or false + * @throws ConnectorException the connector exception + */ + @Override + public Boolean changePassword(Map configData, Map data) throws ConnectorException { + // write your logic to change password in the target system + LOGGER.setLevel(Level.ALL); + LOGGER.info("Change password"); + + // Log the content of data map and mask the value of password field for debug purpose + AS400Utils.printConfigData(data, "data"); + + // Process configuration to replace binding variables and "inject" dynamic attributes + Map config_map = porcessConfigJSON(configData, data, Constants.CHANGECREDENTIAL_JSON); + + // Log configuration with binding variables resolved and password masked for debug purpose + AS400Utils.printConfigData(config_map, "Processed " + Constants.CHANGECREDENTIAL_JSON); + + // Form change user password command + String command = AS400Utils.constructChangePasswordCommand(config_map); + + boolean success = false; + // Execute change password command + if (sendCommand(connectToAS400(configData), command)) { + //build and return a map as explained in the @return Map description with this method + //map.put("provisioningData",metadata map); + success = true; + } else { + throw new ConnectorException("Failed to change account password."); + } + return success; + } + + /** + * to create user in the target system Example : to create user in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to create user + * in the target system step 4 : return true if successful + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the boolean true or false + * @throws ConnectorException the connector exception + */ + @Override + public Boolean createUser(Map configData, Map data) throws ConnectorException { + LOGGER.setLevel(Level.ALL); + LOGGER.info("Creating user"); + // write your own logic to create user in the target system + return true; + } + + /** + * to update user in the target system Example : to update user in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the query/process with the required input to update user + * in the target system step 4 : return the number of records updated + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the integer number of users updated + * @throws ConnectorException the connector exception + */ + @Override + public Integer updateUser(Map configData, Map data) throws ConnectorException { + // write your own logic to update user in the target system + return 1; + } + + /** + * to create the entitlement in target system Example : to create + * entitlement in the target system , refer to the below steps step 1 : + * retrieve connection attributes from configData/Data step 2 : connect to + * the target system step 3 : execute the query/process with the required + * input to create entitlement in the target system step 4 : return the Map + * with metadata as explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM (metadata + * built with help of reconcile json mapping). Metadata is nothing but set + * of properties from the target system to be updated in SSM for that + * account. Example:Match the corresponding column of SSM with target system + * data and build sampleMetadata. map sampleMetadata to provisioningData key + * in the result map as below and return + * map.put("provisioningData",sampleMetadata) metadata sample format : + * {ENTITLEMENT.COLUMNNAME1=XXXX, ENTITLEMENT.COLUMNNAME2=XXXX, + * ENTITLEMENT.COLUMNNAME3=XXXX} + * @throws ConnectorException the connector exception + */ + @Override + public Map createEntitlement(Map configData, Map data) throws ConnectorException {//Connect to target System With Config data + // create Entitlement in target System + Map map = new HashMap(); + //build and return a map as explained in the @return Map description with this method + //map.put("provisioningData",metadata map); + return map; + } + + /** + * to update the entitlement in target system Example : to update + * entitlement in the target system , refer to the below steps step 1 : + * retrieve connection attributes from configData/Data step 2 : connect to + * the target system step 3 : execute the query/process with the required + * input to update entitlement in the target system step 4 : return the Map + * with metadata as explained in parameter description + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM (metadata + * built with help of reconcile json mapping). Metadata is nothing but set + * of properties from the target system to be updated in SSM for that + * account. Example:Match the corresponding column of SSM with target system + * data and build sampleMetadata. map sampleMetadata to provisioningData key + * in the result map as below and return + * map.put("provisioningData",sampleMetadata) metadata sample format : + * {ENTITLEMENT.COLUMNNAME1=XXXX, ENTITLEMENT.COLUMNNAME2=XXXX, + * ENTITLEMENT.COLUMNNAME3=XXXX} + * @throws ConnectorException the connector exception + */ + @Override + public Map updateEntitlement(Map configData, Map data) throws ConnectorException {//Connect to target System With Config data + // update Entitlement in target System + Map map = new HashMap(); + //build and return a map as explained in the @return Map description with this method + //map.put("provisioningData",metadata map); + return map; + } + + /** + * to validate credentials of the given input from connection Example : to + * validate credentials in the target system , refer to the below steps step + * 1 : retrieve connection attributes from configData/Data step 2 : connect + * to the target system step 3 : execute the query/process with the required + * input to validate credentials in the target system step 4 : return true + * if successful, false if failure + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the boolean true or false + * @throws ConnectorException the connector exception + */ + @Override + public Boolean validateCredentials(Map configData, Map data) + throws ConnectorException { + // write your own logic to validate credentials set in configData with the target system + return true; + } + + /** + * This is for future implementation hence ignore it for now to get the + * summary of number of records for the given input object such as + * accounts.It provides number of accounts,users etc + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return the summary map with object and count as key ,value + */ + @Override + public Map getSummary(Map configData, Map data) { + //write your logic + Map map = new HashMap(); + //fetch account data + //map.put("Account", 10); + return map; + } + + /** + * to remove access to entitlements in the target system Example : to remove + * access to entitlements in the target system , refer to the below steps + * step 1 : retrieve connection attributes from configData/Data step 2 : + * connect to the target system step 3 : execute the query/process with the + * required input to add access to account in the target system step 4 : + * return true if successful, false if failure + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return boolean true or false which indicates success or failure + * @throws ConnectorException the connector exception + */ + @Override + public Boolean removeEntitlement(Map configData, Map data) + throws ConnectorException { + //write your own logic to remove Entitlement in target System + return true; + } + + /** + * to add access to entitlements in the target system Example : to add + * access to entitlements in the target system , refer to the below steps + * step 1 : retrieve connection attributes from configData/Data step 2 : + * connect to the target system step 3 : execute the query/process with the + * required input to add access to account in the target system step 4 : + * return true if successful, false if failure + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return boolean true or false which indicates success or failure + * @throws ConnectorException the connector exception + */ + @Override + public Boolean addAccessToEntitlement(Map configData, Map data) + throws ConnectorException { + //write your own logic to add Access to Entitlement in target System + return true; + } + + /** + * to remove access to entitlements in the target system Example : to remove + * access to entitlements in the target system , refer to the below steps + * step 1 : retrieve connection attributes from configData/Data step 2 : + * connect to the target system step 3 : execute the query/process with the + * required input to add access to account in the target system step 4 : + * return true if successful, false if failure + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Boolean true or false which indicates success or failure + * @throws ConnectorException the connector exception + */ + @Override + public Boolean removeAccessToEntitlement(Map configData, Map data) + throws ConnectorException { + // remove access Entitlement in target System + //Return + return true; + } + + /** + * to provide the firefighterId access to a system/application in target + * system for the input create account connection attributes of connection + * configuration in SSM provisioningData sample format: {null} Example : to + * add firefighterIdGrantAccess(firefighterIdGrantAccess is invoked when + * provisioning job is triggered in SSM) to account in the target system , + * refer to the below steps step 1 : retrieve connection attributes from + * configData/Data step 2 : connect to the target system step 3 : execute + * the process with the required input to add access in the target system + * step 4 : return the map with metadata + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map firefighterIdGrantAccess(Map configData, Map data) + throws ConnectorException {//Connect to target System With Config data + + //write your own logic to grant firefighterId Access in target System + Map map = new HashMap(); + //return null in below Map + map.put("provisioningData", null); + + return map; + } + + /** + * to remove the firefighterId access to a system/application in target + * system for the input create account connection attributes of connection + * configuration in SSM provisioningData sample format: {null} Example : to + * revoke firefighteridaccess(firefighterIdRevokeAccess is invoked when + * provisioning job is triggered in SSM) to account in the target system , + * refer to the below steps step 1 : retrieve connection attributes from + * configData/Data step 2 : connect to the target system step 3 : execute + * the process with the required input to revoke access in the target system + * step 4 : return the map with metadata + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map firefighterIdRevokeAccess(Map configData, Map data) + throws ConnectorException {//Connect to target System With Config data + + //write your own logic to revoke firefighterId Access in target System + Map map = new HashMap(); + //return null in below Map + map.put("provisioningData", null); + + return map; + } + + /** + * to provide the firefighterId instance access to a system/application in + * target system for the input create account connection attributes of + * connection configuration in SSM provisioningData sample format: {null} + * Example : to provide + * firefighterIdInstanceGrantAccess(firefighterIdInstanceGrantAccess is + * invoked immediately upon the task creation in SSM) to account in the + * target system , refer to the below steps step 1 : retrieve connection + * attributes from configData/Data step 2 : connect to the target system + * step 3 : execute the process with the required input to add access in the + * target system step 4 : return the map with metadata + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map firefighterIdInstanceGrantAccess(Map configData, Map data) + throws ConnectorException {//Connect to target System With Config data + + //write your own logic to grant firefighterIdInstance Access in target System + Map map = new HashMap(); + //return null in below Map + map.put("provisioningData", null); + + return map; + } + + /** + * to remove the firefighterId instance access to a system/application in + * target system for the input create account connection attributes of + * connection configuration in SSM provisioningData sample format: {null} + * Example : to revoke + * firefighteridInstanceaccess(firefighterIdInstanceRevokeAccess is invoked + * immediately upon the task creation in SSM) to account in the target + * system , refer to the below steps step 1 : retrieve connection attributes + * from configData/Data step 2 : connect to the target system step 3 : + * execute the process with the required input to revoke access in the + * target system step 4 : return the map with metadata + * + * @param configData the configData This is a metadata that contains the + * details of the information required and configurations needed for + * establishing the connectivity to the target system and for doing + * provisioning and reconciliation operations. This is defined in + * setConfig().These appear as JSON or fields on the UI that have to be + * input at the time of creating the connection for this connector in SSM + * @param data contains the values (input details) of the JSON + * attributes/fields specified at the time of creating the connection for + * this connector in SSM UI. current user/task/entitlement/account data + * referred in input JSON are dynamically populated at the runtime. Along + * with connection attributes, this parameter also contains some additional + * information (key value pairs) that can be used during + * provisioning,reconciliation etc. e.g IMPORTABLE_OBJECT - This signifies + * whether account recon or user recon is happening. Valid values + * ("ACCOUNT","USER") endpointId - contains endpoint Id for the endpoint + * corresponding to this connector + * @return Map which consists of metadata to be updated in SSM.This is for + * future implementation hence set it to null for now Example: + * map.put("provisioningData",null) + * @throws ConnectorException the connector exception + */ + @Override + public Map firefighterIdInstanceRevokeAccess(Map configData, Map data) + throws ConnectorException {//Connect to target System With Config data + + //write your own logic to revoke access to firefighterId Instance in target System + Map map = new HashMap(); + //return null in below Map + map.put("provisioningData", null); + + return map; + } + +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Utils.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Utils.java new file mode 100644 index 0000000..045ce96 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/AS400Utils.java @@ -0,0 +1,599 @@ +package com.saviynt.connectorms.as400; + +import com.ibm.as400.access.User; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Map; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import java.util.List; +import org.slf4j.LoggerFactory; +import com.saviynt.connectorms.as400.util.Constants; +import com.saviynt.connectorms.as400.util.RandomPasswordGenerator; +import com.saviynt.connectorms.as400.util.PasswordPolicy; +import com.saviynt.ssm.abstractConnector.exceptions.ConnectorException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * + * @author marcozhang + */ +public class AS400Utils { + + private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(AS400Utils.class.getName()); + + protected static String constructCreateUserCommand(Map attributes) { + LOGGER.setLevel(Level.ALL); + + String command = "CRTUSRPRF"; + + if (attributes != null) { + String accountName = (String) attributes.get("USRPRF"); + command += " USRPRF(" + accountName + ")"; + String attribList = constructUserAttributeParameters(attributes); + if (attribList.length() > 0) { + command += " " + attribList; + } + } + + return command; + } + + protected static String constructUpdateUserCommand(Map attributes) { + LOGGER.setLevel(Level.ALL); + String command = "CHGUSRPRF"; + + if (attributes != null) { + String accountName = (String) attributes.get("USRPRF"); + command += " USRPRF(" + accountName + ")"; + String attribList = constructUserAttributeParameters(attributes); + if (attribList.length() > 0) { + command += " " + attribList; + } + } + + return command; + } + + protected static String constructDeleteUserCommand(Map attributes, String gpfName) { + LOGGER.setLevel(Level.ALL); + String command = "DLTUSRPRF"; + + if (attributes != null) { + String accountName = (String) attributes.get("USRPRF"); + command += " USRPRF(" + accountName + ")"; + String attribList = constructDeleteUserAttributeParameters(attributes, gpfName); + if (attribList.length() > 0) { + command += " " + attribList; + } + } + + return command; + } + + protected static String constructUpdateGroupCommand(Map attributes, List groups) { + LOGGER.setLevel(Level.ALL); + String command = "CHGUSRPRF"; + + if (attributes != null) { + String accountName = (String) attributes.get("USRPRF"); + command += " USRPRF(" + accountName + ")"; + String attribList = constructUserAttributeParameters(attributes); + if (attribList.length() > 0) { + command += " " + attribList; + } + } + command += " SUPGRPPRF(" + String.join(" ", groups) + ")"; + + return command; + } + + protected static String constructChangePasswordCommand(Map attributes) { + LOGGER.setLevel(Level.ALL); + String command = "CHGUSRPRF"; + + if (attributes != null) { + String accountName = (String) attributes.get("USRPRF"); + String password = (String) attributes.get(Constants.SAV_JSONATTR_NEWPASSWORD); + command += " USRPRF(" + accountName + ") PASSWORD(" + password + ")"; + + } + + return command; + } + + /** + * Builds a list of parameters appropriate for the resource to either create + * or update user. + * + * @return a complete formatted parameter list for use in creating users + * @param attributes get the parameter values from attribute mapping + */ + protected static String constructUserAttributeParameters(Map attributes) { + StringBuilder paramList = new StringBuilder(); + + int i = 0; + int size = attributes.size(); + for (Map.Entry attribute : attributes.entrySet()) { + i++; + String attrName = attribute.getKey(); + Object attrValue = attribute.getValue(); + //LOGGER.info("attrName: " + attrName + " attrValue: " + attrValue.toString()); + // Only put the parameter into command if its allowed and its value isn't empty + if (isAllowableCreateUserAttribute(attrName) && attrValue != null && !attrValue.toString().trim().isEmpty()) { + String cmd_attr = constructUserAttributeParameter(attrName, attrValue.toString()); + paramList.append(cmd_attr); + if (i < size) { + paramList.append(" "); + } + //LOGGER.info("paramList: " + paramList); + } + + } + + return paramList.toString(); + } + + /** + * Builds a list of parameters appropriate for the resource to either delete + * user. + * + * @return a complete formatted parameter list for use in creating users + * @param attributes get the parameter values from attribute mapping + * @param gpfName get the parameter values for group profile name of the + * user to be deleted + */ + protected static String constructDeleteUserAttributeParameters(Map attributes, String gpfName) { + StringBuilder paramList = new StringBuilder(); + + String ownObjOpt = (String) attributes.get(Constants.OPT_OWNOBJOPT); + if (ownObjOpt != null && !ownObjOpt.isEmpty()) { + String objectInheritor = null; + + if (ownObjOpt.equalsIgnoreCase("NODLT") || ownObjOpt.equalsIgnoreCase("*NODLT")) { + paramList.append("OWNOBJOPT(*NODLT)"); + } else if (ownObjOpt.equalsIgnoreCase("DLT") || ownObjOpt.equalsIgnoreCase("*DLT")) { + paramList.append("OWNOBJOPT(*DLT)"); + } else if (ownObjOpt.toUpperCase().startsWith("CHGOWN") || ownObjOpt.toUpperCase().startsWith("*CHGOWN")) { + // See if a user profile name is specified after CHGOWN option. + // If so use the profile name specified + String[] opts = ownObjOpt.split("\\s+"); + if (opts.length == 2) { + objectInheritor = opts[1]; + } else { + if (gpfName != null && !gpfName.equalsIgnoreCase("*NONE")) { + objectInheritor = gpfName; + } else { + // If *CHGOWN is specified without following by a owner name, and user's doesn't have group profile assigned + // throws error + LOGGER.error("GRPPRF required for CHGOWN Option"); + throw new ConnectorException("GRPPRF required for CHGOWN Option"); + } + } + paramList.append("OWNOBJOPT(*CHGOWN ").append(objectInheritor).append(")"); + } + + } else { + // we don't want to orphan the user's objects so we make sure someone inherits them. + // First we see if they have a group profile. If so we reassign user's objects to the + // group profile. if not we reassign user's objects to default owner + String objectInheritor = gpfName; + if (objectInheritor == null || objectInheritor.equalsIgnoreCase("*NONE")) { + objectInheritor = "QDFTOWN"; + } + paramList.append("OWNOBJOPT(*CHGOWN ").append(objectInheritor).append(")"); + } + + String pgpOpt = (String) attributes.get(Constants.OPT_PGPOPT); + if (pgpOpt != null && !pgpOpt.isEmpty()) { + String objectInheritor = null; + // If OWNOBJOPT is already added the command parameter, let's add a space for cater for PGPOPT option + if (paramList.lastIndexOf("OWNOBJOPT") != -1) { + paramList.append(" "); + } + if (pgpOpt.equalsIgnoreCase("NOCHG") || pgpOpt.equalsIgnoreCase("*NOCHG")) { + paramList.append("PGPOPT(*NOCHG)"); + } else if (pgpOpt.toUpperCase().startsWith("CHGPGP") || pgpOpt.toUpperCase().startsWith("*CHGPGP")) { + String[] opts = pgpOpt.split("\\s+"); + if (opts.length == 2) { + objectInheritor = opts[1]; + } else { + LOGGER.error("Missing user profile name for PGPOPT Option"); + throw new ConnectorException("Missing user profile name for PGPOPT Option"); + } + paramList.append("PGPOPT(*CHGPGP ").append(objectInheritor).append(")"); + } + } + + return paramList.toString(); + } + + /** + * Reports whether a given attribute name is legal on the create. This is + * useful for implementing either exclusion lists, or inclusion lists. + * + * @return true if attribute is allowable for user creation, false if not + * @param name the name of the attribute to check + */ + protected static boolean isAllowableCreateUserAttribute(String name) { + // Verify if the attribute is valid for create user profile command + return !name.equalsIgnoreCase(Constants.OPT_USRPRF) + //&& !name.equalsIgnoreCase(OPT_PASSWORD) + && !name.equalsIgnoreCase("Group") + && !name.equalsIgnoreCase(Constants.OPT_PASSWORDLASTCHANGEDDATE) + && !name.equalsIgnoreCase(Constants.OPT_SIGNEDONATTEMPTSNOTVALID); + } + + /** + * Builds a AS400 compatible command argument. + * + * @return a single formatted key value parameter option + * @param key the parameter name + * @param value the parameter value + */ + protected static String constructUserAttributeParameter(String key, String value) { + if (value == null) { + return ""; + } + String param; + /* + if (key.equalsIgnoreCase(Constants.OPT_TEXT)) { + param = key.toUpperCase() + "('" + value + "')"; + } else { + param = key.toUpperCase() + "(" + value + ")"; + } + */ + + switch (key.toUpperCase()) { + case Constants.OPT_TEXT: + // If this TEXT attribute, we need to quote the value string to cater for space + // The command string should look like TEXT('description text...') + param = key.toUpperCase() + "('" + value + "')"; + break; + case Constants.OPT_SPCAUT: + case Constants.OPT_SETJOBATR: + case Constants.OPT_USROPT: + // If this is SPCAUT, SETJOBATR or USROPT attribute, we expect comma-separated string like "*ALLOBJ,*JOBCTL" + // The command string should look like SPCAUT(*ALLOBJ *JOBCTL) + String[] arr = value.split(","); + ArrayList list = new ArrayList<>(Arrays.asList(arr)); + param = key.toUpperCase() + "(" + String.join(" ", list) + ")"; + break; + default: + // For the rest of attribute with single value + // The command string should look like STATUS(*ENABLE) + param = key.toUpperCase() + "(" + value + ")"; + } + + return param; + } + + protected static Object getParamValue(User u, String param) { + Object value = null; + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + //System.out.println("as400ParamName: " + param.toUpperCase()); + //LOGGER.info("as400ParamName: " + param.toUpperCase()); + if (u != null && param != null) { + switch (param.toUpperCase()) { + case Constants.OPT_USRPRF: + value = u.getUserProfileName(); + break; + case Constants.OPT_UID: + value = Long.toString(u.getUserID()); + break; + case Constants.OPT_TEXT: + value = u.getDescription(); + break; + case Constants.OPT_PREVIOUSSIGNEDONDATE: + if (u.getPreviousSignedOnDate() != null) { + value = dateFormat.format(u.getPreviousSignedOnDate()); + } + break; + case Constants.OPT_PASSWORDLASTCHANGEDDATE: + if (u.getPasswordLastChangedDate() != null) { + value = dateFormat.format(u.getPasswordLastChangedDate()); + } + break; + case Constants.OPT_SIGNEDONATTEMPTSNOTVALID: + value = Integer.toString(u.getSignedOnAttemptsNotValid()); + break; + case Constants.OPT_USREXPDATE: + if (u.getUserExpirationDate() != null) { + value = dateFormat.format(u.getUserExpirationDate()); + } + break; + case Constants.OPT_USRCLS: + value = u.getUserClassName(); + break; + case Constants.OPT_ASTLVL: + value = u.getAssistanceLevel(); + break; + case Constants.OPT_CURLIB: + value = u.getCurrentLibraryName(); + break; + case Constants.OPT_INLPGM: + value = u.getInitialProgram(); + break; + case Constants.OPT_INLMNU: + value = u.getInitialMenu(); + break; + case Constants.OPT_LMTCPB: + value = u.getLimitCapabilities(); + break; + case Constants.OPT_SPCAUT: + value = Arrays.toString(u.getSpecialAuthority()); + break; + case Constants.OPT_SPCENV: + value = u.getSpecialEnvironment(); + break; + case Constants.OPT_DSPSGNINF: + value = u.getDisplaySignOnInformation(); + break; + case Constants.OPT_PWDEXPITV: + value = Integer.toString(u.getPasswordExpirationInterval()); + break; + case Constants.OPT_PWDCHGBLK: + value = u.getPasswordChangeBlock(); + break; + case Constants.OPT_LMTDEVSSN: + value = u.getLimitDeviceSessions(); + break; + case Constants.OPT_KBDBUF: + value = u.getKeyboardBuffering(); + break; + case Constants.OPT_MAXSTG: + value = Integer.toString(u.getMaximumStorageAllowed()); + break; + case Constants.OPT_MAXSTGLRG: + value = Long.toString(u.getMaximumStorageAllowedInLong()); + break; + case Constants.OPT_OWNER: + value = u.getOwner(); + break; + case Constants.OPT_GRPAUT: + value = u.getGroupAuthority(); + break; + case Constants.OPT_GRPAUTTYP: + value = u.getGroupAuthorityType(); + break; + case Constants.OPT_GRPPRF: + value = u.getGroupProfileName(); + break; + case Constants.OPT_SUPGRPPRF: + value = Arrays.toString(u.getSupplementalGroups()); + break; + case Constants.OPT_ACGCDE: + value = u.getAccountingCode(); + break; + case Constants.OPT_MSGQ: + value = u.getMessageQueue(); + break; + case Constants.OPT_PRTDEV: + value = u.getPrintDevice(); + break; + case Constants.OPT_OUTQ: + value = u.getOutputQueue(); + break; + case Constants.OPT_ATNPGM: + value = u.getAttentionKeyHandlingProgram(); + break; + case Constants.OPT_SRTSEQ: + value = u.getSortSequenceTable(); + break; + case Constants.OPT_LANGID: + value = u.getLanguageID(); + break; + case Constants.OPT_CNTRYID: + value = u.getCountryID(); + break; + case Constants.OPT_CCSID: + value = Integer.toString(u.getCCSID()); + break; + case Constants.OPT_CHRIDCTL: + value = u.getCHRIDControl(); + break; + case Constants.OPT_STATUS: + value = u.getStatus(); + break; + case Constants.OPT_SETJOBATR: + value = Arrays.toString(u.getLocaleJobAttributes()); + break; + case Constants.OPT_LOCALE: + value = u.getLocalePathName(); + break; + case Constants.OPT_USROPT: + value = Arrays.toString(u.getUserOptions()); + break; + case Constants.OPT_GID: + value = Long.toString(u.getGroupID()); + break; + case Constants.OPT_HOMEDIR: + value = u.getHomeDirectory(); + break; + case Constants.OPT_USREXPITV: + value = Integer.toString(u.getUserExpirationInterval()); + break; + case Constants.OPT_JOBD: + value = u.getJobDescription(); + break; + case Constants.OPT_PTYLMT: + value = Integer.toString(u.getHighestSchedulingPriority()); + default: + } + } + + return value; + } + + public static Map uppercaseMapKey(Map map) { + Map newmap = new HashMap(); + for (Map.Entry attribute : map.entrySet()) { + String key = attribute.getKey(); + String value = (String) attribute.getValue(); + newmap.put(key.toUpperCase(), value); + } + + return newmap; + } + + public static Map getAttributeMappings(Map configData, String jsonConfigName) { + // Get account or group attributes configuration + Object attributes_config = configData.get(jsonConfigName); + // Initiaise json to avoid null pointer exception + JSONObject attributes_json = new JSONObject(); + if (attributes_config != null && !attributes_config.toString().isEmpty()) { + try { + attributes_json = new JSONObject(attributes_config.toString()); + } catch (JSONException ex) { + LOGGER.debug("Json format error in " + jsonConfigName + ". " + ex.getMessage()); + throw new ConnectorException("Json format error in " + jsonConfigName + ". " + ex.getMessage()); + } + } + + // Retrieve colsToPropsMap section of the account or group configuration + Map orig_attributes_map = attributes_json.has(Constants.COLSTOPROPSMAP) ? attributes_json.getJSONObject(Constants.COLSTOPROPSMAP).toMap() : new HashMap(); + + // Convert EIC account or entitlement property names to upper case to avoid poential property not found issue + Map attributes_map = uppercaseMapKey(orig_attributes_map); + //LOGGER.info("{} attributes_map: {}", jsonConfigName, attributes_map); + + return attributes_map; + } + + /* + * if dynamic attributes are defined for the endpoint, they appear in data object when account creation task is created from request. + * However, dynamic attributes aren't in data object if account creation task is created from birthright rule. This will cause exception + * during execution of the task when GroovyService.convertTemplateToString is invoked. + * To workaround the issue, we need to define all dynamic attributes in the connection configuration and "inject" them into data object. + * DynamicAttributesJSON is in the following format + * { + * "d_USRCLS": "", + * "d_ASTLVL": "" + * } + */ + public static Map addDynamicAttributes(Map configData, Map data) { + Map newdata = data; + + // Retrieve dynamic attribute names from DynamicAttributesJSON configuration + Object dynamicAttributes_config = configData.get(Constants.DYNAMICATTRIBUTES_JSON); + //Convert json string to map + JSONObject dynamicAttributes_json = new JSONObject(); + if (dynamicAttributes_config != null && !dynamicAttributes_config.toString().isEmpty()) { + try { + dynamicAttributes_json = new JSONObject(dynamicAttributes_config.toString()); + } catch (JSONException ex) { + LOGGER.debug("Json format error in " + Constants.DYNAMICATTRIBUTES_JSON + ". " + ex.getMessage()); + throw new ConnectorException("Json format error in " + Constants.DYNAMICATTRIBUTES_JSON + ". " + ex.getMessage()); + } + } + Map dynamicAttributes_map = dynamicAttributes_json.toMap(); + + for (Map.Entry attribute : dynamicAttributes_map.entrySet()) { + String key = attribute.getKey(); + // Test if the dynamic attribute already exist. If so, do nothing + if (!newdata.containsKey(key)) { + newdata.put(key, attribute.getValue()); + } + } + + return newdata; + } + + /* + * Print configData and data objects and mask sensitive value + */ + protected static void printConfigData(Map data, String mapName) { + LOGGER.debug("###### {} map ######", mapName); + for (Map.Entry attribute : data.entrySet()) { + String key = attribute.getKey(); + Object value = attribute.getValue(); + Object output = value; + + if (key.equalsIgnoreCase("Password") || key.equalsIgnoreCase("randomPassword") || key.equalsIgnoreCase("newAccountPassword")) { + // If it sensitive data mask the value + output = "*****"; + } else { + // If it is json string, convert it beforing printing + try { + JSONObject jsonObject = new JSONObject(value.toString()); + output = jsonObject.toString(); + } catch (JSONException ex) { + // do nothing + } + } + LOGGER.debug(key + " = " + output); + } + } + + public static String generatePassword(Map configData) { + Object pwdPolicy_config = configData.get(Constants.PASSWORDPOLICY_JSON); + JSONObject pwdPolicy_json = new JSONObject(); + + if (pwdPolicy_config != null && !pwdPolicy_config.toString().isEmpty()) { + try { + pwdPolicy_json = new JSONObject(pwdPolicy_config.toString()); + } catch (JSONException ex) { + LOGGER.debug("Json format error in " + Constants.PASSWORDPOLICY_JSON + ". " + ex.getMessage()); + throw new ConnectorException("Json format error in " + Constants.PASSWORDPOLICY_JSON + ". " + ex.getMessage()); + } + } + + Map pwdPolicy_map = pwdPolicy_json.toMap(); + int minLength = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MINLENGTH, 8); + int maxLength = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MAXLENGTH, 10); + int minNumeric = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MINNUMERIC, 1); + int minLower = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MINLOWER, 1); + int minUpper = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MINUPPER, 1); + int minSpec = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MINSPECIAL, 1); + int maxRepeat = (int) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_MAXREPEATED, 2); + String specialChars = (String) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_SPECIALCHARS, "!@#$%^&*_=+-/"); + boolean allowfirst = (boolean) pwdPolicy_map.getOrDefault(Constants.PWD_POLICY_ALLOWFIRSTDIGITORSPECIAL, false); + /* + PasswordGenerator pwdGen = new PasswordGenerator(); + pwdGen.setSPLCHARS(specialChars); + pwd = pwdGen.generatePassword(minLength, maxLength, minLower, minUpper, minNumeric, minSpec); + */ + PasswordPolicy customPolicy = new PasswordPolicy(); + customPolicy.setMinLength(minLength); + customPolicy.setMaxLength(maxLength); + customPolicy.setMinLowercase(minLower); + customPolicy.setMinUppercase(minUpper); + customPolicy.setMinDigits(minNumeric); + customPolicy.setMinSpecialChars(minSpec); + customPolicy.setMaxRepeatedChars(2); + customPolicy.setSpecialCharacters(specialChars); + customPolicy.setMaxRepeatedChars(maxRepeat); + customPolicy.setAllowFirstDigitOrSpeicalChar(allowfirst); + String pwd = RandomPasswordGenerator.generatePassword(customPolicy); + + return pwd; + } + + /* + * Mask password in the command + */ + public static String maskPassword(String inputString) { + // Regex to find the PASSWORD field + Pattern pattern = Pattern.compile("PASSWORD\\(([^)]+)\\)"); + Matcher matcher = pattern.matcher(inputString); + + // Buffer to build the result + StringBuffer result = new StringBuffer(); + + // Iterate over all matches + while (matcher.find()) { + // Replace the password with fixed mask of asterisks + matcher.appendReplacement(result, "PASSWORD(******)"); + } + // Append the tail of the input string + matcher.appendTail(result); + + return result.toString(); + } + +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/Utils.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/Utils.java new file mode 100644 index 0000000..3fdd596 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/Utils.java @@ -0,0 +1,108 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.saviynt.connectorms.as400; + +import com.ibm.as400.access.AS400SecurityException; +import com.ibm.as400.access.ErrorCompletingRequestException; +import com.ibm.as400.access.ObjectDoesNotExistException; +import com.ibm.as400.access.RequestNotSupportedException; +import com.ibm.as400.access.UserList; +import com.ibm.as400.access.User; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Enumeration; + +/** + * + * @author marcozhang + */ +public class Utils { + + public static void main(String[] args) + throws AS400SecurityException, ErrorCompletingRequestException, InterruptedException, IOException, ObjectDoesNotExistException, RequestNotSupportedException { + + AS400Connection conn = null; + + try { + conn = new AS400Connection("pub400.com", "marcozj", "rr4mXqAE"); + + //RUserList list = new RUserList(conn.getConnection()); + //list.setSelectionValue(RUserList.SELECTION_CRITERIA, RUserList.USER); + //Enumeration users = list.resources(); + + UserList users = new UserList(conn.getConnection()); + Enumeration io = users.getUsers(); + + while (io.hasMoreElements()) { + User u = (User) io.nextElement(); + System.out.println("User name" + ": " + u.getName()); + System.out.println("User profile name / User (USRPRF)" + ": " + u.getUserProfileName()); + System.out.println("Previous sign-on date" + ": " + u.getPreviousSignedOnDate()); + System.out.println("Password last change date" + ": " + u.getPasswordLastChangedDate()); + System.out.println("Status (STATUS)" + ": " + u.getStatus()); + System.out.println("Password verification not valid" + ": " + u.getSignedOnAttemptsNotValid()); + System.out.println("User class / Type of User (USRCLS)" + ": " + u.getUserClassName()); + System.out.println("Assistance level (ASTLVL)" + ": " + u.getAssistanceLevel()); + System.out.println("Current library / Default library (CURLIB)" + ": " + u.getCurrentLibraryName()); + System.out.println("Initial program / Sign on program (INLPGM)" + ": " + u.getInitialProgram()); + System.out.println("Initial menu / First menu (INLMNU)" + ": " + u.getInitialMenu()); + System.out.println("Limit capabilities / Restrict command line use (LMTCPB)" + ": " + u.getLimitCapabilities()); + System.out.println("Text / User description (TEXT)" + ": " + u.getDescription()); + System.out.println("Special authority (SPCAUT)" + ": " + Arrays.toString(u.getSpecialAuthority())); + System.out.println("Special environment (SPCENV)" + ": " + u.getSpecialEnvironment()); + System.out.println("Display sign-on information (DSPSGNINF)" + ": " + u.getDisplaySignOnInformation()); + System.out.println("Password expiration interval (PWDEXPITV)" + ": " + u.getPasswordExpirationInterval()); + System.out.println("Block Password Change (PWDCHGBLK)" + ": " + u.getPasswordChangeBlock()); + //System.out.println("Local password management (LCLPWDMGT)" + ": " + u.); + System.out.println("Limit device sessions (LMTDEVSSN)" + ": " + u.getLimitDeviceSessions()); + System.out.println("Keyboard buffering (KBDBUF)" + ": " + u.getKeyboardBuffering()); + System.out.println("Maximum storage (MAXSTG)" + ": " + u.getMaximumStorageAllowed()); + System.out.println("Maximum large storage (MAXSTGLRG)" + ": " + u.getMaximumStorageAllowedInLong()); + //System.out.println("Priority limit (PTYLMT)" + ": " + u.); + System.out.println("Job description (JOBD)" + ": " + u.getJobDescription()); + System.out.println("Group profile / User Group (GRPPRF)" + ": " + u.getGroupProfileName()); + System.out.println("Owner (OWNER)" + ": " + u.getOwner()); + System.out.println("Group authority (GRPAUT)" + ": " + u.getGroupAuthority()); + System.out.println("Group authority type (GRPAUTTYP)" + ": " + u.getGroupAuthorityType()); + System.out.println("Supplemental groups (SUPGRPPRF)" + ": " + Arrays.toString(u.getSupplementalGroups())); + System.out.println("Accounting code (ACGCDE)" + ": " + u.getAccountingCode()); + //System.out.println("Document password (DOCPWD)" + ": " + u.); + System.out.println("Message queue (MSGQ)" + ": " + u.getMessageQueue()); + //System.out.println("Delivery (DLVRY)" + ": " + u.get); + //System.out.println("Severity (SEV)" + ": " + u.); + System.out.println("Print device / Default printer (PRTDEV)" + ": " + u.getPrintDevice()); + System.out.println("Output queue (OUTQ)" + ": " + u.getOutputQueue()); + System.out.println("Attention-Key-Handling program (ATNPGM)" + ": " + u.getAttentionKeyHandlingProgram()); + System.out.println("Sort Sequence (SRTSEQ)" + ": " + u.getSortSequenceTable()); + System.out.println("Language identifier (LANGID)" + ": " + u.getLanguageID()); + System.out.println("Country or region identifier (CNTRYID)" + ": " + u.getCountryID()); + System.out.println("Coded character set identifier (CCSID)" + ": " + u.getCCSID()); + System.out.println("Character identifier control (CHRIDCTL)" + ": " + u.getCHRIDControl()); + System.out.println("Job attributes (SETJOBATR)" + ": " + Arrays.toString(u.getLocaleJobAttributes())); + System.out.println("Locale (LOCALE)" + ": " + u.getLocalePathName()); + System.out.println("User Options (USROPT)" + ": " + Arrays.toString(u.getUserOptions())); + System.out.println("User identification number (UID)" + ": " + u.getUserID()); + System.out.println("Group identification number (GID)" + ": " + u.getGroupID()); + System.out.println("Home directory (HOMEDIR)" + ": " + u.getHomeDirectory()); + //System.out.println("EIM association (EIMASSOC)" + ": " + u.get); + System.out.println("User expiration date (USREXPDATE)" + ": " + u.getUserExpirationDate()); + System.out.println("User expiration interval (USREXPITV)" + ": " + u.getUserExpirationInterval()); + //System.out.println("Authority (AUT)" + ": " + u.ge); + + + System.out.println(""); + } + + } finally { + if (conn != null) { + conn.close(); + } + } + + } + +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/exceptions/ConfigurationException.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/exceptions/ConfigurationException.java new file mode 100644 index 0000000..774b2d7 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/exceptions/ConfigurationException.java @@ -0,0 +1,22 @@ +package com.saviynt.connectorms.as400.exceptions; + +import com.saviynt.ssm.abstractConnector.exceptions.ConnectorException; + +public class ConfigurationException extends ConnectorException { + private static final long serialVersionUID = 1L; + + public ConfigurationException() { + } + + public ConfigurationException(String message) { + super(message); + } + + public ConfigurationException(Throwable cause) { + super(cause); + } + + public ConfigurationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/Constants.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/Constants.java new file mode 100644 index 0000000..82c5400 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/Constants.java @@ -0,0 +1,105 @@ +package com.saviynt.connectorms.as400.util; + +public class Constants { + // Contstants used by connector configuration + public static final String HOSTNAME = "Host"; + public static final String USERNAME = "Username"; + public static final String PASSWORD = "Password"; + public static final String USESSL = "useSSL"; + public static final String ENTITLEMENT_TYPE_GROUP = "Group"; + public static final String ACCOUNT_PREFIX = "ACCOUNT."; + public static final String ENTITLEMENT_PREFIX = "ENTITLEMENT."; + public static final String ACCOUNT_ATTRIBUTES_JSON = "AccountAttributesJSON"; + public static final String GROUP_ATTRIBUTES_JSON = "GroupAttributesJSON"; + public static final String COLSTOPROPSMAP = "colsToPropsMap"; + public static final String RESPCOLSTOPROPSMAP = "responseColsToPropsMap"; + public static final String COMMAND_ATTRIBUTES = "commandAttributes"; + public static final String CREATEACCOUNT_JSON = "CreateAccountJSON"; + public static final String UPDATEACCOUNT_JSON = "UpdateAccountJSON"; + public static final String REMOVEACCOUNT_JSON = "RemoveAccountJSON"; + public static final String ENABLEACCOUNT_JSON = "EnableAccountJSON"; + public static final String DISABLEACCOUNT_JSON = "DisableAccountJSON"; + public static final String ADDACCESS_JSON = "AddAccessJSON"; + public static final String REMOVEACCESS_JSON = "RemoveAccessJSON"; + public static final String CHANGECREDENTIAL_JSON = "ChangeCredentialJSON"; + // Tried PasswordPolicyJSON and PwdPolicyJSON but its value is masked in the UI so using PWPolicyJSON instead to workaround the silly issue + public static final String PASSWORDPOLICY_JSON = "PWPolicyJSON"; + public static final String DYNAMICATTRIBUTES_JSON = "DynamicAttributesJSON"; + public static final String SETRANDOMPASSWORD = "SETRANDOMPASSWORD"; + + public static final String PWD_POLICY_MINLENGTH = "minLength"; + public static final String PWD_POLICY_MAXLENGTH = "maxLength"; + public static final String PWD_POLICY_MINNUMERIC = "minNumeric"; + public static final String PWD_POLICY_MINLOWER = "minLowercaseChars"; + public static final String PWD_POLICY_MINUPPER = "minUppercaseChars"; + public static final String PWD_POLICY_MINSPECIAL = "minSpecialChars"; + public static final String PWD_POLICY_SPECIALCHARS = "specialChars"; + public static final String PWD_POLICY_MAXREPEATED = "maxRepeatedChars"; + public static final String PWD_POLICY_ALLOWFIRSTDIGITORSPECIAL = "allowFirstDigitOrSpecialChar"; + + public static final String SAV_ACCTATTR_ACCOUNTID = "ACCOUNTID"; + public static final String SAV_ACCTATTR_NAME = "NAME"; + public static final String SAV_ENTATR_ENTITLEMENTTYPE = "TYPE"; + public static final String SAV_ENTATR_ENTITLEMENTID = "ENTITLEMENTID"; + public static final String SAV_ENTATR_ENTITLEMENT_VALUE = "ENTITLEMENT_VALUE"; + public static final String SAV_JSONATTR_GROUP = "Group"; + public static final String SAV_JSONATTR_NEWPASSWORD = "newAccountPassword"; + + // Constants used by AS400 configuration and attribute mapping + public static final String OPT_RECONCILIATION_FIELD = "RECONCILIATION_FIELD"; + public static final String OPT_DEFAULT_RECONCILIATION_FIELD = "ACCOUNTID"; + public static final String OPT_USRPRF = "USRPRF"; + public static final String OPT_PASSWORD = "PASSWORD"; + public static final String OPT_ACGCDE = "ACGCDE"; + public static final String OPT_ASTLVL = "ASTLVL"; + public static final String OPT_ATNPGM = "ATNPGM"; + public static final String OPT_CCSID = "CCSID"; + public static final String OPT_CHRIDCTL = "CHRIDCTL"; + public static final String OPT_CNTRYID = "CNTRYID"; + public static final String OPT_CURLIB = "CURLIB"; + public static final String OPT_DSPSGNINF = "DSPSGNINF"; + public static final String OPT_GRPAUT = "GRPAUT"; + public static final String OPT_GRPAUTTYP = "GRPAUTTYP"; + public static final String OPT_GID = "GID"; + public static final String OPT_GRPPRF = "GRPPRF"; + public static final String OPT_HOMEDIR = "HOMEDIR"; + public static final String OPT_INLMNU = "INLMNU"; + public static final String OPT_INLPGM = "INLPGM"; + public static final String OPT_JOBD = "JOBD"; + public static final String OPT_KBDBUF = "KBDBUF"; + public static final String OPT_LANGID = "LANGID"; + public static final String OPT_LMTCPB = "LMTCPB"; + public static final String OPT_LMTDEVSSN = "LMTDEVSSN"; + public static final String OPT_SETJOBATR = "SETJOBATR"; + public static final String OPT_LOCALE = "LOCALE"; + public static final String OPT_MAXSTG = "MAXSTG"; + public static final String OPT_MAXSTGLRG = "MAXSTGLRG"; + public static final String OPT_DLVRY = "DLVRY"; + public static final String OPT_MSGQ = "MSGQ"; + public static final String OPT_SEV = "SEV"; + public static final String OPT_OUTQ = "OUTQ"; + public static final String OPT_OWNER = "OWNER"; + public static final String OPT_PWDEXPITV = "PWDEXPITV"; + public static final String OPT_PRTDEV = "PRTDEV"; + public static final String OPT_PWDEXP = "PWDEXP"; + public static final String OPT_SRTSEQ = "SRTSEQ"; + public static final String OPT_SPCAUT = "SPCAUT"; + public static final String OPT_SPCENV = "SPCENV"; + public static final String OPT_STATUS = "STATUS"; + public static final String OPT_SUPGRPPRF = "SUPGRPPRF"; + public static final String OPT_TEXT = "TEXT"; + public static final String OPT_USRCLS = "USRCLS"; + public static final String OPT_UID = "UID"; + public static final String OPT_USROPT = "USROPT"; + public static final String OPT_OWNOBJOPT = "OWNOBJOPT"; + public static final String OPT_PGPOPT = "PGPOPT"; + public static final String OPT_USREXPDATE = "USREXPDATE"; + public static final String OPT_PWDCHGBLK = "PWDCHGBLK"; + public static final String OPT_USREXPITV = "USREXPITV"; + public static final String OPT_PTYLMT = "PTYLMT"; + public static final String OPT_PREVIOUSSIGNEDONDATE = "PREVIOUSSIGNEDONDATE"; + public static final String OPT_PASSWORDLASTCHANGEDDATE = "PASSWORDLASTCHANGEDDATE"; + public static final String OPT_SIGNEDONATTEMPTSNOTVALID = "SIGNEDONATTEMPTSNOTVALID"; + public static final String OPT_DOCPWD = "DOCPWD"; + public static final String OPT_EIMASSOC = "EIMASSOC"; +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordGenerator.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordGenerator.java new file mode 100644 index 0000000..311eaa8 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordGenerator.java @@ -0,0 +1,69 @@ +package com.saviynt.connectorms.as400.util; + +import java.security.SecureRandom; + +/** + * + * @author marcozhang + */ +public class PasswordGenerator { + + private final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private final String ALPHA = "abcdefghijklmnopqrstuvwxyz"; + private final String NUM = "0123456789"; + private String SPL_CHARS = "!@#$%^&*_=+-/"; + + public void setSPLCHARS(String str) { + SPL_CHARS = str; + } + + public char[] generatePswd(int minLen, int maxLen, int minLowercaseChars, int minUppercaseChars, int minNumeric, int minSpecialChars) { + + String All = ALPHA_CAPS + ALPHA + NUM + SPL_CHARS; + + if (minLen > maxLen) { + throw new IllegalArgumentException("Maximum length must be greater than or equal to minimum length!"); + } + if ((minLowercaseChars + minUppercaseChars + minNumeric + minSpecialChars) > minLen) { + throw new IllegalArgumentException("Sum of (LOWER, CAPS, DIGITS, SPL CHARS) Length must not be greater than minimum length!"); + } + SecureRandom rnd = new SecureRandom(); + int len = rnd.nextInt(maxLen - minLen + 1) + minLen; + char[] pswd = new char[len]; + for (int i = 0; i < minLowercaseChars; i++) { + int index = getNextIndex(rnd, len, pswd); + pswd[index] = ALPHA.charAt(rnd.nextInt(ALPHA.length())); + } + for (int i = 0; i < minUppercaseChars; i++) { + int index = getNextIndex(rnd, len, pswd); + pswd[index] = ALPHA_CAPS.charAt(rnd.nextInt(ALPHA_CAPS.length())); + } + for (int i = 0; i < minNumeric; i++) { + int index = getNextIndex(rnd, len, pswd); + pswd[index] = NUM.charAt(rnd.nextInt(NUM.length())); + } + for (int i = 0; i < minSpecialChars; i++) { + int index = getNextIndex(rnd, len, pswd); + pswd[index] = SPL_CHARS.charAt(rnd.nextInt(SPL_CHARS.length())); + } + for (int i = 0; i < len; i++) { + if (pswd[i] == 0) { + pswd[i] = All.charAt(rnd.nextInt(All.length())); + } + } + return pswd; + } + + public String generatePassword(int minLen, int maxLen, int minLowercaseChars, int minUppercaseChars, int minNumeric, int minSpecialChars) { + return new String(generatePswd(minLen, maxLen, minLowercaseChars, minUppercaseChars, minNumeric, minSpecialChars)); + } + + private int getNextIndex(SecureRandom rnd, int len, char[] pswd) { + int index = rnd.nextInt(len); + //while (pswd[index = rnd.nextInt(len)] != 0); + while (pswd[index] != 0) { + index = rnd.nextInt(len); + } + return index; + } +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordPolicy.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordPolicy.java new file mode 100644 index 0000000..7c58277 --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/PasswordPolicy.java @@ -0,0 +1,135 @@ +package com.saviynt.connectorms.as400.util; + +/** + * + * @author marcozhang + */ +public class PasswordPolicy { + private int minLength; + private int maxLength; + private int minLowercase; + private int minUppercase; + private int minDigits; + private int minSpecialChars; + private int maxRepeatedChars; + private String lowercaseLetters; + private String uppercaseLetters; + private String digits; + private String specialCharacters; + private boolean allowFirstDigitOrSpecialChar; + + // Constructor with default policy values + public PasswordPolicy() { + this.minLength = 8; + this.maxLength = 16; + this.minLowercase = 1; + this.minUppercase = 1; + this.minDigits = 1; + this.minSpecialChars = 1; + this.maxRepeatedChars = 2; + this.lowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; + this.uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + this.digits = "0123456789"; + this.specialCharacters = "!@#$%^&*()-_=+[]{}|;:,.<>?"; + this.allowFirstDigitOrSpecialChar = true; + } + + // Getter and setter methods for policy values + // (Getters and setters omitted for brevity) + // Getter and setter methods for policy values + public int getMinLength() { + return minLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public int getMaxLength() { + return maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public int getMinLowercase() { + return minLowercase; + } + + public void setMinLowercase(int minLowercase) { + this.minLowercase = minLowercase; + } + + public int getMinUppercase() { + return minUppercase; + } + + public void setMinUppercase(int minUppercase) { + this.minUppercase = minUppercase; + } + + public int getMinDigits() { + return minDigits; + } + + public void setMinDigits(int minDigits) { + this.minDigits = minDigits; + } + + public int getMinSpecialChars() { + return minSpecialChars; + } + + public void setMinSpecialChars(int minSpecialChars) { + this.minSpecialChars = minSpecialChars; + } + + public int getMaxRepeatedChars() { + return maxRepeatedChars; + } + + public void setMaxRepeatedChars(int maxRepeatedChars) { + this.maxRepeatedChars = maxRepeatedChars; + } + + public String getLowercaseLetters() { + return lowercaseLetters; + } + + public void setLowercaseLetters(String lowercaseLetters) { + this.lowercaseLetters = lowercaseLetters; + } + + public String getUppercaseLetters() { + return uppercaseLetters; + } + + public void setUppercaseLetters(String uppercaseLetters) { + this.uppercaseLetters = uppercaseLetters; + } + + public String getDigits() { + return digits; + } + + public void setDigits(String digits) { + this.digits = digits; + } + + public String getSpecialCharacters() { + return specialCharacters; + } + + public void setSpecialCharacters(String specialCharacters) { + this.specialCharacters = specialCharacters; + } + + public boolean isAllowFirstDigitOrSpeicalChar() { + return allowFirstDigitOrSpecialChar; + } + + public void setAllowFirstDigitOrSpeicalChar(boolean allowFirstDigitOrSpeicalChar) { + this.allowFirstDigitOrSpecialChar = allowFirstDigitOrSpeicalChar; + } +} diff --git a/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/RandomPasswordGenerator.java b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/RandomPasswordGenerator.java new file mode 100644 index 0000000..eeb95dd --- /dev/null +++ b/as400/src/AS400/src/main/java/com/saviynt/connectorms/as400/util/RandomPasswordGenerator.java @@ -0,0 +1,253 @@ +package com.saviynt.connectorms.as400.util; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class RandomPasswordGenerator { + + public static String generatePassword(PasswordPolicy policy) { + + if (policy.getMinLength() > policy.getMaxLength()) { + throw new IllegalArgumentException("Maximum length must be greater than or equal to minimum length!"); + } + if ((policy.getMinLowercase() + policy.getMinUppercase() + policy.getMinDigits() + policy.getMinSpecialChars()) > policy.getMinLength()) { + throw new IllegalArgumentException("Sum of (minLowercase, minUppercase, minDigits, minSpecialChars) length must not be greater than minimum length!"); + } + + SecureRandom random = new SecureRandom(); + int length = random.nextInt(policy.getMaxLength() - policy.getMinLength() + 1) + policy.getMinLength(); // Random length between minLength and maxLength + + StringBuilder password = new StringBuilder(); + List chars = new ArrayList<>(); + + // Add required number of lowercase letters + addCharacters(password, chars, policy.getLowercaseLetters(), policy.getMinLowercase(), random); + + // Add required number of uppercase letters + addCharacters(password, chars, policy.getUppercaseLetters(), policy.getMinUppercase(), random); + + // Add required number of digits + addCharacters(password, chars, policy.getDigits(), policy.getMinDigits(), random); + + // Add required number of special characters + addCharacters(password, chars, policy.getSpecialCharacters(), policy.getMinSpecialChars(), random); + + // Add remaining random characters + int remainingLength = length - (policy.getMinLowercase() + policy.getMinUppercase() + policy.getMinDigits() + policy.getMinSpecialChars()); + for (int i = 0; i < remainingLength; i++) { + String characterSet = policy.getLowercaseLetters() + policy.getUppercaseLetters() + policy.getDigits() + policy.getSpecialCharacters(); + char randomChar = characterSet.charAt(random.nextInt(characterSet.length())); + if (!hasReachedMaxRepeat(password, randomChar, policy.getMaxRepeatedChars())) { + password.append(randomChar); + chars.add(randomChar); + } else { + i--; // Re-try to add another random character + } + } + + // Shuffle the password to ensure randomness + return shuffleString(password.toString(), policy.isAllowFirstDigitOrSpeicalChar()); + } + + // Helper method to add required characters to the password + private static void addCharacters(StringBuilder password, List chars, String charSet, int requiredCount, Random random) { + for (int i = 0; i < requiredCount; i++) { + char randomChar = charSet.charAt(random.nextInt(charSet.length())); + password.append(randomChar); + chars.add(randomChar); + } + } + + // Helper method to check if a character has reached the maximum repetition limit + private static boolean hasReachedMaxRepeat(StringBuilder password, char character, int maxRepeatedChars) { + int count = 0; + for (int i = 0; i < password.length(); i++) { + if (password.charAt(i) == character) { + count++; + if (count >= maxRepeatedChars) { + return true; + } + } + } + return false; + } + + // Helper method to shuffle the characters in a string + private static String shuffleString(String string, Boolean allowFirstDigitOrSpeicalChar) { + char[] characters = string.toCharArray(); + SecureRandom random = new SecureRandom(); + + if (allowFirstDigitOrSpeicalChar) { + for (int i = 0; i < characters.length; i++) { + int randomIndex = random.nextInt(characters.length); + char temp = characters[i]; + characters[i] = characters[randomIndex]; + characters[randomIndex] = temp; + } + } else { + // Find the first lowercase or uppercase character in the original string + char firstChar = '\0'; // Initialize with null character + for (char c : characters) { + if (Character.isLowerCase(c) || Character.isUpperCase(c)) { + firstChar = c; + break; + } + } + + // if the original string doesn't have letter at all, swap the first character with a randomly chosen lowercase or uppercase character + if (firstChar == '\0') { + characters[0] = getRandomLetterOrDigit(random); + } + + // Shuffle the rest of the characters + for (int i = 1; i < characters.length; i++) { + int randomIndex = random.nextInt(characters.length - 1) + 1; // Exclude the first character + char temp = characters[i]; + characters[i] = characters[randomIndex]; + characters[randomIndex] = temp; + } + } + + return new String(characters); + } + + // Helper method to get a random letter or digit + private static char getRandomLetterOrDigit(SecureRandom random) { + String lettersAndDigits = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + return lettersAndDigits.charAt(random.nextInt(lettersAndDigits.length())); + } +} +/* +class PasswordPolicy { + + private int minLength; + private int maxLength; + private int minLowercase; + private int minUppercase; + private int minDigits; + private int minSpecialChars; + private int maxRepeatedChars; + private String lowercaseLetters; + private String uppercaseLetters; + private String digits; + private String specialCharacters; + private boolean allowFirstDigitOrSpecialChar; + + // Constructor with default policy values + public PasswordPolicy() { + this.minLength = 8; + this.maxLength = 16; + this.minLowercase = 1; + this.minUppercase = 1; + this.minDigits = 1; + this.minSpecialChars = 1; + this.maxRepeatedChars = 2; + this.lowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; + this.uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + this.digits = "0123456789"; + this.specialCharacters = "!@#$%^&*()-_=+[]{}|;:,.<>?"; + this.allowFirstDigitOrSpecialChar = true; + } + + // Getter and setter methods for policy values + // (Getters and setters omitted for brevity) + // Getter and setter methods for policy values + public int getMinLength() { + return minLength; + } + + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public int getMaxLength() { + return maxLength; + } + + public void setMaxLength(int maxLength) { + this.maxLength = maxLength; + } + + public int getMinLowercase() { + return minLowercase; + } + + public void setMinLowercase(int minLowercase) { + this.minLowercase = minLowercase; + } + + public int getMinUppercase() { + return minUppercase; + } + + public void setMinUppercase(int minUppercase) { + this.minUppercase = minUppercase; + } + + public int getMinDigits() { + return minDigits; + } + + public void setMinDigits(int minDigits) { + this.minDigits = minDigits; + } + + public int getMinSpecialChars() { + return minSpecialChars; + } + + public void setMinSpecialChars(int minSpecialChars) { + this.minSpecialChars = minSpecialChars; + } + + public int getMaxRepeatedChars() { + return maxRepeatedChars; + } + + public void setMaxRepeatedChars(int maxRepeatedChars) { + this.maxRepeatedChars = maxRepeatedChars; + } + + public String getLowercaseLetters() { + return lowercaseLetters; + } + + public void setLowercaseLetters(String lowercaseLetters) { + this.lowercaseLetters = lowercaseLetters; + } + + public String getUppercaseLetters() { + return uppercaseLetters; + } + + public void setUppercaseLetters(String uppercaseLetters) { + this.uppercaseLetters = uppercaseLetters; + } + + public String getDigits() { + return digits; + } + + public void setDigits(String digits) { + this.digits = digits; + } + + public String getSpecialCharacters() { + return specialCharacters; + } + + public void setSpecialCharacters(String specialCharacters) { + this.specialCharacters = specialCharacters; + } + + public boolean isAllowFirstDigitOrSpeicalChar() { + return allowFirstDigitOrSpecialChar; + } + + public void setAllowFirstDigitOrSpeicalChar(boolean allowFirstDigitOrSpeicalChar) { + this.allowFirstDigitOrSpecialChar = allowFirstDigitOrSpeicalChar; + } + +}*/