001package com.pingidentity.accountStatusNotificationHandler; 002 003import com.unboundid.directory.sdk.ds.api.AccountStatusNotificationHandler; 004import com.unboundid.directory.sdk.ds.config.AccountStatusNotificationHandlerConfig; 005import com.unboundid.directory.sdk.ds.types.AccountStatusNotification; 006import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 007import com.unboundid.directory.sdk.sync.types.EndpointException; 008import com.unboundid.ldap.sdk.Attribute; 009import com.unboundid.ldap.sdk.Entry; 010import com.unboundid.ldap.sdk.LDAPException; 011import com.unboundid.ldap.sdk.ResultCode; 012import com.unboundid.util.args.*; 013 014import java.io.*; 015import java.net.HttpURLConnection; 016import java.net.MalformedURLException; 017import java.net.URL; 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.List; 022 023/** 024 * This class performs the necessary processing to handle account status 025 * notifications by triggering flows in SingularKey 026 */ 027public class SingularKey extends AccountStatusNotificationHandler { 028 029 public static final String FLOW_URL_ARG = "flow-url"; 030 public static final String API_KEY_ARG = "api-key"; 031 public static final String SUCCESS_CODE_ARG = "success-code"; 032 private URL url; 033 private String apiKey; 034 private List<Integer> successCodes; 035 036 @Override 037 public String getExtensionName() { 038 return "pd-account-status-notification-singularkey"; 039 } 040 041 @Override 042 public String[] getExtensionDescription() { 043 return new String[]{"Handle account status notification in SingularKey flows"}; 044 } 045 046 @Override 047 public void defineConfigArguments(ArgumentParser parser) throws ArgumentException { 048 IntegerArgument successCodesArg = new IntegerArgument(null, SUCCESS_CODE_ARG,false,0,"{http-code}","HTTP codes to treat as success",Arrays.asList(new Integer[]{200,201,202,204})); 049 parser.addArgument(successCodesArg); 050 StringArgument apiUrlArg = new StringArgument(null, FLOW_URL_ARG, true, 1, "{url}", "The base URL for the instance of SingularKey to trigger flows in. Example: https://example.singularkey.com/v1/company/<companyID>/flows/<flowID>/start"); 051 apiUrlArg.addValueValidator(new URLArgumentValueValidator()); 052 parser.addArgument(apiUrlArg); 053 StringArgument apKeylArg = new StringArgument(null, API_KEY_ARG, true, 1, "{url}", "The API key. This is from the SingularKey App. Not the flow need to be registered with the app before the flow can be started."); 054 parser.addArgument(apKeylArg); 055 } 056 057 @Override 058 public ResultCode applyConfiguration(AccountStatusNotificationHandlerConfig config, ArgumentParser parser, List<String> adminActionsRequired, List<String> messages) { 059 successCodes = parser.getIntegerArgument(SUCCESS_CODE_ARG).getValues(); 060 apiKey = parser.getStringArgument(API_KEY_ARG).getValue(); 061 try { 062 url = new URL(parser.getStringArgument(FLOW_URL_ARG).getValue()); 063 return ResultCode.SUCCESS; 064 } catch (MalformedURLException e) { 065 if ( messages != null ){ 066 messages.add(e.getMessage()); 067 } 068 return ResultCode.OTHER; 069 } 070 } 071 072 @Override 073 public void initializeAccountStatusNotificationHandler(DirectoryServerContext serverContext, AccountStatusNotificationHandlerConfig config, ArgumentParser parser) throws LDAPException { 074 List<String> messages = new ArrayList<>(); 075 ResultCode resultCode = applyConfiguration(config, parser, null, null); 076 if ( ResultCode.SUCCESS != resultCode ){ 077 throw new LDAPException(resultCode,String.join(", ",messages)); 078 } 079 } 080 081 @Override 082 public void handleStatusNotification(AccountStatusNotification notification) { 083 StringBuilder json = new StringBuilder(); 084 json.append('{'); 085 json.append(jsonKey("event")); 086 json.append(jsonValue("account status notification")); 087 json.append(','); 088 json.append(jsonKey("type")); 089 json.append(jsonValue(notification.getNotificationType().getName())); 090 json.append(','); 091 json.append(jsonKey("entry")); 092 json.append(toJSON(notification.getUserEntry().toLDAPSDKEntry())); 093 json.append('}'); 094 try { 095 publish(json.toString(), successCodes); 096 } catch (EndpointException e) { 097 e.printStackTrace(); 098 } 099 } 100 101 private boolean publish(String json, List<Integer> successCodes) throws EndpointException { 102 Boolean result = Boolean.FALSE; 103 HttpURLConnection conn = null; 104 try { 105 conn = (HttpURLConnection) url.openConnection(); 106 conn.setRequestMethod("POST"); 107 conn.setDoOutput(true); 108 conn.setDoInput(true); 109 conn.setUseCaches(false); 110 conn.setAllowUserInteraction(false); 111 conn.setRequestProperty("Content-Type", "application/json"); 112 conn.setRequestProperty("X-SK-API-Key", apiKey); 113 114 OutputStream out = conn.getOutputStream(); 115 Writer writer = new OutputStreamWriter(out, "UTF-8"); 116 writer.write(json); 117 writer.close(); 118 out.close(); 119 120 if (!successCodes.contains(conn.getResponseCode()) ) { 121 throw new IOException("The response code received from the server ["+conn.getResponseCode()+"] did not match any of the expected codes ("+successCodes+")"); 122 } 123 124 // Buffer the result into a string 125 BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 126 StringBuilder sb = new StringBuilder(); 127 String line; 128 while ((line = rd.readLine()) != null) { 129 sb.append(line); 130 } 131 rd.close(); 132 //Do something with the response 133 result = Boolean.TRUE; 134 } catch (IOException e) { 135 if (conn != null){ 136 conn.disconnect(); 137 } 138 throw new EndpointException(new LDAPException(ResultCode.OTHER, e)); 139 } finally { 140 if (conn != null){ 141 conn.disconnect(); 142 } 143 } 144 return result; 145 } 146 147 private static String toJSON(Entry entry){ 148 StringBuilder sb = new StringBuilder(); 149 sb.append('{'); 150 sb.append(jsonKey("_dn")); 151 sb.append(jsonValue(entry.getDN())); 152 Collection<Attribute> attributes = entry.getAttributes(); 153 for (Attribute attribute: attributes){ 154 String[] values = attribute.getValues(); 155 if ( values != null && values.length>0) { 156 sb.append(','); 157 sb.append(jsonKey(attribute.getName())); 158 sb.append(jsonArray(values)); 159 } 160 } 161 sb.append('}'); 162 return sb.toString(); 163 } 164 165 private final static String jsonValue(final String s){ 166 final StringBuilder sb = new StringBuilder(); 167 sb.append('"'); 168 sb.append(s); 169 sb.append('"'); 170 return sb.toString(); 171 } 172 173 private final static String jsonKey(final String s){ 174 final StringBuilder sb = new StringBuilder(); 175 sb.append(jsonValue(s)); 176 sb.append(':'); 177 return sb.toString(); 178 } 179 180 private final static String jsonArray(final String[] ss){ 181 final StringBuilder sb = new StringBuilder(); 182 sb.append('['); 183 sb.append(jsonValue(String.join("\",\"",ss))); 184 sb.append(']'); 185 return sb.toString(); 186 } 187}