MedRecDBMSPlugin.java
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 = (CustomDBMSAuthenticatorMBeanmBean;
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(Stringgroups.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[inull;
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((StringgrpItr.next()))
423           return true;
424       }
425     else {
426       for (Iterator grpItr = groups.iterator(); grpItr.hasNext();) {
427         if (member.equalsIgnoreCase((StringgrpItr.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(Stringgroups.elementAt(i);
467         logDebug("   "+retVal[i]);
468       }
469     }
470     return retVal;
471   }
472 
473 }