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}