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}