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}