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