001package com.identityworksllc.iiq.common.query;
002
003import bsh.EvalError;
004import sailpoint.api.SailPointFactory;
005import sailpoint.object.Application;
006import sailpoint.plugin.PluginBaseHelper;
007import sailpoint.tools.GeneralException;
008import sailpoint.tools.JdbcUtil;
009import sailpoint.tools.Util;
010
011import java.sql.Connection;
012import java.util.HashMap;
013import java.util.Map;
014
015/**
016 * Encapsulates a variety of ways of opening connections to a database,
017 * from Beanshell callbacks to connection info to specifying that we should
018 * use the IIQ or Plugin DB.
019 *
020 * This is mainly an attempt to reduce parameter explosion in other classes.
021 */
022public class ConnectOptions {
023
024    /**
025     * A builder for this object
026     */
027    public static final class ConnectOptionsBuilder {
028
029        private bsh.This bshThis;
030        private Map<String, Object> connectionParams;
031        private String connectionRetrievalMethod;
032        private boolean iiqDatabase;
033        private String password;
034        private boolean pluginDatabase;
035        private String url;
036        private String username;
037
038        private ConnectOptionsBuilder() {
039        }
040
041        public ConnectOptions build() {
042            boolean hasDatabaseParams = false;
043            if (Util.isNotNullOrEmpty(this.connectionRetrievalMethod)) {
044                hasDatabaseParams = true;
045            } else if (!Util.isEmpty(this.connectionParams)) {
046                hasDatabaseParams = true;
047            } else if (!Util.isAnyNullOrEmpty(url, username, password)) {
048                hasDatabaseParams = true;
049            }
050
051            if (!hasDatabaseParams && !iiqDatabase && !pluginDatabase) {
052                throw new IllegalArgumentException("Missing required input: You must specify any of: withIIQDatabase, withPluginDatabase, a connection retrieval method, connection parameters, or (url + username + password)");
053            }
054
055            ConnectOptions connectOptions = new ConnectOptions();
056            connectOptions.pluginDatabase = this.pluginDatabase;
057            connectOptions.bshThis = this.bshThis;
058            connectOptions.iiqDatabase = this.iiqDatabase;
059            connectOptions.connectionRetrievalMethod = this.connectionRetrievalMethod;
060            connectOptions.username = this.username;
061            connectOptions.url = this.url;
062            connectOptions.connectionParams = this.connectionParams;
063            connectOptions.password = this.password;
064            return connectOptions;
065        }
066
067        public ConnectOptionsBuilder withBshThis(bsh.This bshThis) {
068            this.bshThis = bshThis;
069            return this;
070        }
071
072        public ConnectOptionsBuilder withConnectionParams(Map<String, Object> connectionParams) {
073            this.connectionParams = connectionParams;
074            return this;
075        }
076
077        public ConnectOptionsBuilder withConnectionRetrievalMethod(String connectionRetrievalMethod) {
078            this.connectionRetrievalMethod = connectionRetrievalMethod;
079            return this;
080        }
081
082        public ConnectOptionsBuilder withIiqDatabase() {
083            this.iiqDatabase = true;
084            return this;
085        }
086
087        public ConnectOptionsBuilder withPassword(String password) {
088            this.password = password;
089            return this;
090        }
091
092        public ConnectOptionsBuilder withPluginDatabase() {
093            this.pluginDatabase = true;
094            return this;
095        }
096
097        public ConnectOptionsBuilder withUrl(String url) {
098            this.url = url;
099            return this;
100        }
101
102        public ConnectOptionsBuilder withUsername(String username) {
103            this.username = username;
104            return this;
105        }
106    }
107
108    /**
109     * Creates a new builder for a {@link ConnectOptions}
110     * @return The connect options object
111     */
112    public static ConnectOptionsBuilder builder() {
113        return new ConnectOptionsBuilder();
114    }
115
116    /**
117     * Gets connect options from an Application, where they ought to be stored in the
118     * standard IIQ format expected by {@link JdbcUtil#getConnection(Map)}.
119     * @param application The application object
120     * @return A populated ConnectOptions
121     */
122    public static ConnectOptions fromApplication(Application application) {
123        if (application == null || application.getAttributes() == null) {
124            throw new IllegalArgumentException("Non-null application attributes expected in ConnectOptions.fromApplication()");
125        }
126        return builder().withConnectionParams(application.getAttributes()).build();
127    }
128
129    /**
130     * Gets connect options from a Map, i.e., one stored in configuration
131     * @param input The input connect options
132     * @return if anything fails
133     */
134    public static ConnectOptions fromMap(Map<String, Object> input) {
135        ConnectOptionsBuilder builder = new ConnectOptionsBuilder();
136
137        if (input.get("username") != null) {
138            builder.withUsername((String) input.get("username"));
139        } else if (input.get("url") != null) {
140            builder.withUrl((String) input.get("url"));
141        } else if (input.get("password") != null) {
142            builder.withPassword((String) input.get("password"));
143        } else if (input.get("connectionParams") instanceof Map) {
144            builder.withConnectionParams((Map<String, Object>) input.get("connectionParams"));
145        } else if (Util.otob(input.get("iiqDatabase"))) {
146            builder.withIiqDatabase();
147        } else if (Util.otob(input.get("pluginDatabase"))) {
148            builder.withPluginDatabase();
149        }
150
151        return builder.build();
152    }
153
154    /**
155     * The 'this' callback, used to invoke Beanshell code from Java
156     */
157    private bsh.This bshThis;
158    /**
159     * The connection params
160     */
161    private Map<String, Object> connectionParams;
162    /**
163     * Gets the method used to retrieve a DB connection
164     */
165    private String connectionRetrievalMethod;
166    /**
167     * True if we should connect to the IIQ database
168     */
169    private boolean iiqDatabase;
170    /**
171     * The password used to retrieve a DB connection. This can be Sailpoint-encrypted
172     * and will be decrypted only as needed to open a connection.
173     */
174    private String password;
175    /**
176     * True if we should connect to the plugin database
177     */
178    private boolean pluginDatabase;
179    /**
180     *
181     */
182    private String url;
183    /**
184     * The username to connect to the DB
185     */
186    private String username;
187
188    /**
189     * The constructor used by the builder
190     */
191    private ConnectOptions() {
192        /* basic constructor */
193    }
194
195    /**
196     * Copy constructor
197     * @param other The other object
198     */
199    public ConnectOptions(ConnectOptions other) {
200        this.bshThis = other.bshThis;
201
202        if (other.connectionParams != null) {
203            this.connectionParams = new HashMap<>(other.connectionParams);
204        }
205
206        this.connectionRetrievalMethod = other.connectionRetrievalMethod;
207
208        this.iiqDatabase = other.iiqDatabase;
209
210        this.password = other.password;
211
212        this.pluginDatabase = other.pluginDatabase;
213
214
215        this.url = other.url;
216        this.username = other.username;
217    }
218
219    /**
220     * Opens a connection to the database. The caller is responsible for closing it.
221     *
222     * @return The connection to the database
223     * @throws GeneralException if anything fails
224     */
225    public Connection openConnection() throws GeneralException {
226        if (iiqDatabase) {
227            return ContextConnectionWrapper.getConnection();
228        } else if (pluginDatabase) {
229            return PluginBaseHelper.getConnection();
230        } else if (Util.isNotNullOrEmpty(connectionRetrievalMethod)) {
231            if (bshThis == null) {
232                throw new IllegalArgumentException("Cannot specify a connectionRetrievalMethod without also specifying a bsh.This");
233            }
234            try {
235                return (Connection) bshThis.invokeMethod(connectionRetrievalMethod, new Object[0]);
236            } catch(EvalError e) {
237                throw new GeneralException(e);
238            }
239        } else if (!Util.isEmpty(connectionParams)) {
240            return JdbcUtil.getConnection(connectionParams);
241        } else {
242            if (Util.isAnyNullOrEmpty(url, username, password)) {
243                throw new IllegalArgumentException("You must specify a 'url', 'username', and 'password', and one of them was null or empty");
244            }
245
246            String decryptedPassword = password;
247
248            if (decryptedPassword.contains("ACP")) {
249                decryptedPassword = SailPointFactory.getCurrentContext().decrypt(password);
250            }
251
252            return JdbcUtil.getConnection(null, null, url, username, decryptedPassword);
253        }
254    }
255
256}