001 package com.bea.medrec.security;
002
003 import java.sql.PreparedStatement;
004 import java.sql.ResultSet;
005 import java.sql.SQLException;
006 import java.util.Iterator;
007 import java.util.Properties;
008 import java.util.Vector;
009 import weblogic.logging.LoggingHelper;
010 import weblogic.logging.WLLevel;
011 import weblogic.management.security.ProviderMBean;
012 import weblogic.security.providers.authentication.CustomDBMSAuthenticatorMBean;
013 import weblogic.security.providers.authentication.CustomDBMSAuthenticatorPlugin;
014
015 /**
016 * Sample plugin for the CustomDBMSAuthenticatorPlugin ATN provider that matches
017 * the MedRec ATN provider SQL modified for the test system to use the same SQL
018 * that the SQL providers use.
019 *
020 * @author Copyright (c) 2005 by BEA Systems. All Rights Reserved.
021 */
022 public class MedRecDBMSPlugin implements CustomDBMSAuthenticatorPlugin {
023
024 /**
025 * Default SQL that corresponds to the MedRec sample DBMS
026 *
027 * private String GET_PASSWORD_STMT = "SELECT password FROM medrec_user
028 * WHERE username = ? AND status = 'ACTIVE'";
029 * private String PASSWORD_COLUMN = "password";
030 * private String GET_GROUPS_STMT = "SELECT group_name FROM groups groups
031 * WHERE groups.username = ?";
032 * private String GROUP_NAME_COLUMN = "group_name";
033 *
034 *
035 * Default SQL that corresponds to the WLS SQL based DBMS providers
036 *
037 * private String GET_PASSWORD_STMT = "SELECT U_PASSWORD FROM users
038 * WHERE U_NAME = ?";
039 * private String PASSWORD_COLUMN = "U_PASSWORD";
040 * private String GET_GROUPS_STMT = "SELECT G_NAME FROM groupmembers
041 * WHERE G_MEMBER = ?";
042 * private String GROUP_NAME_COLUMN = "G_NAME";
043 */
044
045 private String GET_PASSWORD_STMT = "SELECT password FROM medrec_user WHERE "+
046 "username = ? AND status = 'ACTIVE'";
047 private String PASSWORD_COLUMN = "password";
048 private String GET_GROUPS_STMT = "SELECT group_name FROM groups groups WHERE "+
049 "groups.username = ?";
050 private String GROUP_NAME_COLUMN = "group_name";
051
052 private int maxRecursionDepth = -1;
053 private boolean caseSensitive = false;
054
055 private CustomDBMSAuthenticatorMBean pluggableMBean = null;
056
057 /**
058 * Executed on initialization of the CustomDBMSAuthenticatorPlugin.
059 * @param mBean Properties configured for the plugin
060 */
061 public synchronized void initialize(ProviderMBean mBean) {
062 logDebug("MedRecDBMSPlugin.initialize() called");
063
064 if (mBean == null) {
065 logDebug("No mBean supplied, using default setting values");
066 return;
067 }
068 if (!(mBean instanceof CustomDBMSAuthenticatorMBean)) {
069 logDebug("Not a pluggable runtime provider MBean, using default "+
070 "setting values");
071 return;
072 }
073
074 pluggableMBean = (CustomDBMSAuthenticatorMBean) mBean;
075 getMBeanInfo();
076 }
077
078 /**
079 * Executed on shutdown of the authentication provider, or if the
080 * plugin is replaced dynamically at runtime with another implementation class.
081 */
082 public synchronized void shutdown() {
083 logDebug("MedRecDBMSPlugin.shutdown() called");
084 }
085
086 /**
087 * Called during authentication process to retrieve password for user.
088 *
089 * @param connection JDBC connection
090 * @param user String representing the username
091 * @return String representing the password in one of the supported formats
092 * @exception SQLException if a database access error occurs
093 */
094 public synchronized String lookupPassword(java.sql.Connection connection,
095 String user)
096 throws java.sql.SQLException {
097 logDebug("MedRecDBMSPlugin.lookupPassword() called");
098 logDebug("MedRecDBMSPlugin: Looking up user: "+user);
099
100 if ((connection == null) || (user == null)) {
101 logError("MedRecDBMSPlugin.lookupPassword() failure, connection or "+
102 "user was null");
103 return null;
104 }
105
106 PreparedStatement stmt = null;
107 ResultSet rs = null;
108 String password = null;
109 try {
110 logDebug("MedRecDBMSPlugin.lookupPassword() SQL is: "+
111 GET_PASSWORD_STMT);
112 stmt = connection.prepareStatement(GET_PASSWORD_STMT);
113 stmt.setString(1, user);
114 rs = stmt.executeQuery();
115 if (rs.next())
116 password = rs.getString(PASSWORD_COLUMN);
117 } catch (SQLException e) {
118 logWarn("MedRecDBMSPlugin.lookupPassword() exception", e);
119 throw e;
120 } finally {
121 cleanup(rs, stmt);
122 }
123 return password;
124 }
125
126 /**
127 * Called during Identity Assertion to verify existence of user.
128 *
129 * @param connection JDBC connection
130 * @param user String representing the username
131 * @return boolean indicating user exists or not. true if user exists,
132 * false if it doesn't
133 * @exception SQLException if a database access error occurs
134 */
135 public synchronized boolean userExists(java.sql.Connection connection,
136 String user)
137 throws java.sql.SQLException {
138 logDebug("MedRecDBMSPlugin.userExists() called");
139 if ((connection == null) || (user == null)) {
140 logError("MedRecDBMSPlugin.userExists() failure, connection or user "+
141 "was null");
142 return false;
143 }
144 String password = lookupPassword(connection, user);
145 if (password != null) {
146 logInfo("MedRecDBMSPlugin.userExists() found user "+user);
147 return true;
148 }
149
150 logInfo("MedRecDBMSPlugin.userExists() didn't find user "+user);
151 return false;
152 }
153
154 /**
155 * Called during authentication and identity assertion to determine the users
156 * group membership.
157 *
158 * @param connection Connection to the database
159 * @param user String representing the username
160 * @return an array of group strings, or null for no groups.
161 * @exception SQLException if a database access error occurs
162 */
163 public synchronized String[] lookupUserGroups(java.sql.Connection connection,
164 String user)
165 throws java.sql.SQLException {
166 logDebug("MedRecDBMSPlugin.lookupUserGroups() called");
167 if ((connection == null) || (user == null)) {
168 logError("MedRecDBMSPlugin.lookupUserGroups() failure, connection or "+
169 "user was null");
170 return null;
171 }
172
173 PreparedStatement stmt = null;
174 Vector<String> groups = new Vector<String>();
175 try {
176 logDebug("MedRecDBMSPlugin.lookupUserGroups() SQL : "+GET_GROUPS_STMT);
177 stmt = connection.prepareStatement(GET_GROUPS_STMT);
178 findMemberGroups(stmt, user, groups, 0);
179 } catch (SQLException e) {
180 logError("MedRecDBMSPlugin.lookupUserGroups() Exception!", e);
181 throw e;
182 } finally {
183 cleanup(stmt);
184 }
185
186 logInfo("MedRecDBMSPlugin.lookupUserGroups() found "+groups.size()+
187 " groups:");
188 String[] retVal = null;
189 if (groups.size() > 0) {
190 retVal = new String[groups.size()];
191 for (int i = 0; i < groups.size(); i++) {
192 retVal[i] = (String) groups.elementAt(i);
193 logDebug(" "+retVal[i]);
194 }
195 }
196 return retVal;
197 }
198
199 // Implementation specific utility class for cleaning up statements and results
200 private synchronized void cleanup(ResultSet pResultSet, PreparedStatement pStmt)
201 throws SQLException {
202 try {
203 cleanup(pResultSet);
204 } catch (SQLException e) {
205 logWarn("Cleanup failed on ResultSet", e);
206 throw e;
207 } finally {
208 cleanup(pStmt);
209 }
210 }
211
212 // Implementation specific utility class for cleaning up results
213 private synchronized void cleanup(ResultSet pResultSet)
214 throws SQLException {
215 try {
216 if (pResultSet != null) {
217 pResultSet.close();
218 pResultSet = null;
219 }
220 } catch (SQLException e) {
221 logWarn("Cleanup failed on ResultSet", e);
222 throw e;
223 }
224 }
225
226 // Implementation specific utility class for cleaning up statements
227 private synchronized void cleanup(PreparedStatement pStmt)
228 throws SQLException {
229 try {
230 if (pStmt != null) {
231 pStmt.close();
232 pStmt = null;
233 }
234 } catch (SQLException e) {
235 logWarn("Cleanup failed on Statement", e);
236 throw e;
237 }
238 }
239
240 // Implementation specific utility class for debug logging
241 private void logDebug(String msg) {
242 LoggingHelper.getServerLogger().log(WLLevel.DEBUG, msg);
243 }
244
245 // Implementation specific utility class for error logging
246 private void logError(String msg) {
247 LoggingHelper.getServerLogger().log(WLLevel.ERROR, msg);
248 }
249
250 // Implementation specific utility class for error logging
251 private void logError(String msg, Throwable th) {
252 LoggingHelper.getServerLogger().log(WLLevel.ERROR, msg, th);
253 }
254
255 // Implementation specific utility class for warn logging
256 private void logWarn(String msg, Throwable th) {
257 LoggingHelper.getServerLogger().log(WLLevel.WARNING, msg, th);
258 }
259
260 // Implementation specific utility class for warn logging
261 private void logWarn(String msg) {
262 LoggingHelper.getServerLogger().log(WLLevel.WARNING, msg);
263 }
264
265 // Implementation specific utility class for general logging
266 private void logInfo(String msg) {
267 LoggingHelper.getServerLogger().log(WLLevel.INFO, msg);
268 }
269
270 // Implementation specific utility to get settings from MBean
271 private synchronized void getMBeanInfo() {
272
273 // Sub plugins are not required to have any properties, but if the
274 // sub plugin has special settings that it would like to represent in
275 // the configuration, the properties are how to do that.
276 //
277 // The sample subplugin allows SQL statements and column names to be
278 // specified as Properties, so look for those
279 Properties testProperties = pluggableMBean.getPluginProperties();
280 if (testProperties != null) {
281 String testPropertyValue = null;
282
283 testPropertyValue = testProperties.getProperty("GET_PASSWORD_STMT");
284 if (testPropertyValue != null) {
285 logDebug("GET_PASSWORD_STMT = "+testPropertyValue);
286 GET_PASSWORD_STMT = testPropertyValue;
287 }
288 testPropertyValue = testProperties.getProperty("PASSWORD_COLUMN");
289 if (testPropertyValue != null) {
290 logDebug("PASSWORD_COLUMN = "+testPropertyValue);
291 PASSWORD_COLUMN = testPropertyValue;
292 }
293 testPropertyValue = testProperties.getProperty("GET_GROUPS_STMT");
294 if (testPropertyValue != null) {
295 logDebug("GET_GROUPS_STMT = "+testPropertyValue);
296 GET_GROUPS_STMT = testPropertyValue;
297 }
298 testPropertyValue = testProperties.getProperty("GROUP_NAME_COLUMN");
299 if (testPropertyValue != null) {
300 logDebug("GROUP_NAME_COLUMN = "+testPropertyValue);
301 GROUP_NAME_COLUMN = testPropertyValue;
302 }
303 } else {
304 logDebug("No properties specified for sub-plugin");
305 }
306
307 /** Sub plugins are expected to handle any group membership recursion performed
308 internally. This means that they may not handle it at all or they may
309 handle it in their own way. If they do handle group membership recursion, they
310 should make an attempt to handle the group membership recursion limits that
311 are configured (but they can ignore those settings if they choose).
312
313 The sample handles group membership recursion and honors the recursion limit
314 settings, so get those now. */
315 String testGroupMembershipSearching =
316 pluggableMBean.getGroupMembershipSearching();
317 if (testGroupMembershipSearching != null) {
318 if (testGroupMembershipSearching.equalsIgnoreCase("limited")) {
319 int testMaxRecursionDepth =
320 pluggableMBean.getMaxGroupMembershipSearchLevel().intValue();
321 if (testMaxRecursionDepth < 0) {
322 logDebug("MaxGroupMembershipSearchLevel was invalid "+
323 testMaxRecursionDepth);
324 } else {
325 maxRecursionDepth = testMaxRecursionDepth;
326 logDebug("MaxGroupMembershipSearchLevel is "+maxRecursionDepth);
327 }
328 } else if (testGroupMembershipSearching.equalsIgnoreCase("unlimited")) {
329 logDebug("Unlimited GroupMembershipSearching");
330 maxRecursionDepth = -1;
331 } else {
332 logDebug("Unrecognized GroupMembershipSearching setting");
333 }
334 }
335 return;
336 }
337
338 // Implementation specific utility for handling recursive group membership lookups
339 // FIXME: There needs to be more thought around how to pass along the groups
340 // efficiently. There is too much conversion going on and searching for
341 // names in an unordered vector is to check for cycles is very expensive
342 private final void findMemberGroups(PreparedStatement statement,
343 String name,
344 Vector<String> resultGroups,
345 int depth)
346 throws SQLException {
347 // Check for bad inputs
348 if (statement == null) {
349 String msg = new String("No SQL Statement found for retrieving groups. "+
350 "Check configuration");
351 logDebug(msg);
352 throw new SQLException(msg);
353 }
354 if ((name == null) || (resultGroups == null)) {
355 logDebug("Invalid parameters to findMemberGroups");
356 return;
357 }
358
359 logDebug("findMemberGroups for "+name);
360
361 // Lookup groups in the database
362 String[] results = null;
363 try {
364 results = queryMemberGroups(statement, name);
365 } catch (SQLException se) {
366 logWarn("SQL Exception when retrieving groups", se);
367 throw se;
368 }
369
370 if ((results == null) || (results.length == 0)) {
371 logInfo("No groups found in database for "+name);
372 return;
373 }
374
375 // Add the results to the running list
376 for (int i = 0; i < results.length; i++) {
377 if (memberAlreadyInGroups(caseSensitive, results[i], resultGroups)) {
378 logDebug("Cycle detected, not recursing further for: "+name);
379 results[i] = null;
380 continue;
381 }
382 resultGroups.add(results[i]);
383 }
384
385 // If the depth will be exceeded by more recursion stop now
386 if ((maxRecursionDepth != -1) && (depth >= maxRecursionDepth)) {
387 logDebug("recursive membership search depth limit reached");
388 return;
389 }
390
391 // If we got here, then we got some new group names and we need to
392 // check their group memberships recursively
393 for (int i = 0; i < results.length; i++) {
394 if (results[i] != null)
395 findMemberGroups(statement, results[i], resultGroups, depth+1);
396 }
397 return;
398 }
399
400 private final boolean namesMatch(boolean caseSensitive,
401 String name1,
402 String name2) {
403 if (caseSensitive)
404 return name1.equals(name2);
405 return name1.equalsIgnoreCase(name2);
406 }
407
408 private final boolean memberAlreadyInGroups(boolean caseSensitive,
409 String member,
410 Vector groups) {
411 // If we don't have a member we're done, and the caller likely is too
412 if (member == null)
413 return true;
414
415 // If we have a user and no groups yet, it isn't in there
416 if ((groups == null) || (groups.size() == 0))
417 return false;
418
419 // FIXME: Really inefficient, maybe can use a hashmap or something here...
420 if (caseSensitive) {
421 for (Iterator grpItr = groups.iterator(); grpItr.hasNext();) {
422 if (member.equals((String) grpItr.next()))
423 return true;
424 }
425 } else {
426 for (Iterator grpItr = groups.iterator(); grpItr.hasNext();) {
427 if (member.equalsIgnoreCase((String) grpItr.next()))
428 return true;
429 }
430 }
431 return false;
432 }
433
434 private final String[] queryMemberGroups(PreparedStatement statement,
435 String userOrGroup)
436 throws java.sql.SQLException {
437 logDebug("MedRecDBMSPlugin.queryMemberGroups() called");
438 if ((statement == null) || (userOrGroup == null)) {
439 logDebug("MedRecDBMSPlugin.queryMemberGroups() failure, prepared "+
440 "statement or user/group was null");
441 return null;
442 }
443
444 ResultSet rs = null;
445 Vector<String> groups = new Vector<String>();
446 try {
447 logDebug("MedRecDBMSPlugin.queryMemberGroups() execute lookup statement");
448 statement.setString(1, userOrGroup);
449 rs = statement.executeQuery();
450 while (rs.next()) {
451 groups.addElement(rs.getString(GROUP_NAME_COLUMN));
452 }
453 } catch (SQLException e) {
454 logWarn("MedRecDBMSPlugin.queryMemberGroups() Exception!", e);
455 throw e;
456 } finally {
457 cleanup(rs);
458 }
459
460 logDebug("MedRecDBMSPlugin.lookupUserGroups() found "+groups.size()+
461 " groups:");
462 String[] retVal = null;
463 if (groups.size() > 0) {
464 retVal = new String[groups.size()];
465 for (int i = 0; i < groups.size(); i++) {
466 retVal[i] = (String) groups.elementAt(i);
467 logDebug(" "+retVal[i]);
468 }
469 }
470 return retVal;
471 }
472
473 }
|