001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.realm;
018:
019: import java.security.Principal;
020: import java.sql.Connection;
021: import java.sql.PreparedStatement;
022: import java.sql.ResultSet;
023: import java.sql.SQLException;
024: import java.util.ArrayList;
025:
026: import javax.naming.Context;
027: import javax.sql.DataSource;
028:
029: import org.apache.naming.ContextBindings;
030: import org.apache.catalina.LifecycleException;
031: import org.apache.catalina.ServerFactory;
032: import org.apache.catalina.core.StandardServer;
033: import org.apache.catalina.util.StringManager;
034:
035: /**
036: *
037: * Implmentation of <b>Realm</b> that works with any JDBC JNDI DataSource.
038: * See the JDBCRealm.howto for more details on how to set up the database and
039: * for configuration options.
040: *
041: * @author Glenn L. Nielsen
042: * @author Craig R. McClanahan
043: * @author Carson McDonald
044: * @author Ignacio Ortega
045: * @version $Revision: 1.8 $
046: */
047:
048: public class DataSourceRealm extends RealmBase {
049:
050: // ----------------------------------------------------- Instance Variables
051:
052: /**
053: * The generated string for the roles PreparedStatement
054: */
055: private StringBuffer preparedRoles = null;
056:
057: /**
058: * The generated string for the credentials PreparedStatement
059: */
060: private StringBuffer preparedCredentials = null;
061:
062: /**
063: * The name of the JNDI JDBC DataSource
064: */
065: protected String dataSourceName = null;
066:
067: /**
068: * Descriptive information about this Realm implementation.
069: */
070: protected static final String info = "org.apache.catalina.realm.DataSourceRealm/1.0";
071:
072: /**
073: * Context local datasource.
074: */
075: protected boolean localDataSource = false;
076:
077: /**
078: * Descriptive information about this Realm implementation.
079: */
080: protected static final String name = "DataSourceRealm";
081:
082: /**
083: * The column in the user role table that names a role
084: */
085: protected String roleNameCol = null;
086:
087: /**
088: * The string manager for this package.
089: */
090: protected static final StringManager sm = StringManager
091: .getManager(Constants.Package);
092:
093: /**
094: * The column in the user table that holds the user's credintials
095: */
096: protected String userCredCol = null;
097:
098: /**
099: * The column in the user table that holds the user's name
100: */
101: protected String userNameCol = null;
102:
103: /**
104: * The table that holds the relation between user's and roles
105: */
106: protected String userRoleTable = null;
107:
108: /**
109: * The table that holds user data.
110: */
111: protected String userTable = null;
112:
113: // ------------------------------------------------------------- Properties
114:
115: /**
116: * Return the name of the JNDI JDBC DataSource.
117: *
118: */
119: public String getDataSourceName() {
120: return dataSourceName;
121: }
122:
123: /**
124: * Set the name of the JNDI JDBC DataSource.
125: *
126: * @param dataSourceName the name of the JNDI JDBC DataSource
127: */
128: public void setDataSourceName(String dataSourceName) {
129: this .dataSourceName = dataSourceName;
130: }
131:
132: /**
133: * Return if the datasource will be looked up in the webapp JNDI Context.
134: */
135: public boolean getLocalDataSource() {
136: return localDataSource;
137: }
138:
139: /**
140: * Set to true to cause the datasource to be looked up in the webapp JNDI
141: * Context.
142: *
143: * @param localDataSource the new flag value
144: */
145: public void setLocalDataSource(boolean localDataSource) {
146: this .localDataSource = localDataSource;
147: }
148:
149: /**
150: * Return the column in the user role table that names a role.
151: *
152: */
153: public String getRoleNameCol() {
154: return roleNameCol;
155: }
156:
157: /**
158: * Set the column in the user role table that names a role.
159: *
160: * @param roleNameCol The column name
161: */
162: public void setRoleNameCol(String roleNameCol) {
163: this .roleNameCol = roleNameCol;
164: }
165:
166: /**
167: * Return the column in the user table that holds the user's credentials.
168: *
169: */
170: public String getUserCredCol() {
171: return userCredCol;
172: }
173:
174: /**
175: * Set the column in the user table that holds the user's credentials.
176: *
177: * @param userCredCol The column name
178: */
179: public void setUserCredCol(String userCredCol) {
180: this .userCredCol = userCredCol;
181: }
182:
183: /**
184: * Return the column in the user table that holds the user's name.
185: *
186: */
187: public String getUserNameCol() {
188: return userNameCol;
189: }
190:
191: /**
192: * Set the column in the user table that holds the user's name.
193: *
194: * @param userNameCol The column name
195: */
196: public void setUserNameCol(String userNameCol) {
197: this .userNameCol = userNameCol;
198: }
199:
200: /**
201: * Return the table that holds the relation between user's and roles.
202: *
203: */
204: public String getUserRoleTable() {
205: return userRoleTable;
206: }
207:
208: /**
209: * Set the table that holds the relation between user's and roles.
210: *
211: * @param userRoleTable The table name
212: */
213: public void setUserRoleTable(String userRoleTable) {
214: this .userRoleTable = userRoleTable;
215: }
216:
217: /**
218: * Return the table that holds user data..
219: *
220: */
221: public String getUserTable() {
222: return userTable;
223: }
224:
225: /**
226: * Set the table that holds user data.
227: *
228: * @param userTable The table name
229: */
230: public void setUserTable(String userTable) {
231: this .userTable = userTable;
232: }
233:
234: // --------------------------------------------------------- Public Methods
235:
236: /**
237: * Return the Principal associated with the specified username and
238: * credentials, if there is one; otherwise return <code>null</code>.
239: *
240: * If there are any errors with the JDBC connection, executing
241: * the query or anything we return null (don't authenticate). This
242: * event is also logged, and the connection will be closed so that
243: * a subsequent request will automatically re-open it.
244: *
245: * @param username Username of the Principal to look up
246: * @param credentials Password or other credentials to use in
247: * authenticating this username
248: */
249: public Principal authenticate(String username, String credentials) {
250:
251: Connection dbConnection = null;
252:
253: try {
254:
255: // Ensure that we have an open database connection
256: dbConnection = open();
257: if (dbConnection == null) {
258: // If the db connection open fails, return "not authenticated"
259: return null;
260: }
261:
262: // Acquire a Principal object for this user
263: Principal principal = authenticate(dbConnection, username,
264: credentials);
265:
266: if (!dbConnection.getAutoCommit()) {
267: dbConnection.commit();
268: }
269:
270: // Release the database connection we just used
271: close(dbConnection);
272: dbConnection = null;
273:
274: // Return the Principal (if any)
275: return (principal);
276:
277: } catch (SQLException e) {
278:
279: // Log the problem for posterity
280: log(sm.getString("dataSourceRealm.exception"), e);
281:
282: // Close the connection so that it gets reopened next time
283: if (dbConnection != null)
284: close(dbConnection);
285:
286: // Return "not authenticated" for this request
287: return (null);
288:
289: }
290:
291: }
292:
293: // -------------------------------------------------------- Package Methods
294:
295: // ------------------------------------------------------ Protected Methods
296:
297: /**
298: * Return the Principal associated with the specified username and
299: * credentials, if there is one; otherwise return <code>null</code>.
300: *
301: * @param dbConnection The database connection to be used
302: * @param username Username of the Principal to look up
303: * @param credentials Password or other credentials to use in
304: * authenticating this username
305: *
306: * @exception SQLException if a database error occurs
307: */
308: protected Principal authenticate(Connection dbConnection,
309: String username, String credentials) throws SQLException {
310:
311: ResultSet rs = null;
312: PreparedStatement stmt = null;
313: ArrayList list = null;
314:
315: try {
316: // Look up the user's credentials
317: String dbCredentials = null;
318: stmt = credentials(dbConnection, username);
319: rs = stmt.executeQuery();
320: while (rs.next()) {
321: dbCredentials = rs.getString(1);
322: }
323: rs.close();
324: rs = null;
325: stmt.close();
326: stmt = null;
327: if (dbCredentials == null) {
328: return (null);
329: }
330: dbCredentials = dbCredentials.trim();
331:
332: // Validate the user's credentials
333: boolean validated = false;
334: if (hasMessageDigest()) {
335: // Hex hashes should be compared case-insensitive
336: validated = (digest(credentials)
337: .equalsIgnoreCase(dbCredentials));
338: } else
339: validated = (digest(credentials).equals(dbCredentials));
340:
341: if (validated) {
342: if (debug >= 2)
343: log(sm.getString(
344: "dataSourceRealm.authenticateSuccess",
345: username));
346: } else {
347: if (debug >= 2)
348: log(sm.getString(
349: "dataSourceRealm.authenticateFailure",
350: username));
351: return (null);
352: }
353:
354: // Accumulate the user's roles
355: list = new ArrayList();
356: stmt = roles(dbConnection, username);
357: rs = stmt.executeQuery();
358: while (rs.next()) {
359: list.add(rs.getString(1).trim());
360: }
361: } finally {
362: if (rs != null) {
363: rs.close();
364: }
365: if (stmt != null) {
366: stmt.close();
367: }
368: }
369:
370: // Create and return a suitable Principal for this user
371: return (new GenericPrincipal(this , username, credentials, list));
372:
373: }
374:
375: /**
376: * Close the specified database connection.
377: *
378: * @param dbConnection The connection to be closed
379: */
380: protected void close(Connection dbConnection) {
381:
382: // Do nothing if the database connection is already closed
383: if (dbConnection == null)
384: return;
385:
386: // Close this database connection, and log any errors
387: try {
388: dbConnection.close();
389: } catch (SQLException e) {
390: log(sm.getString("dataSourceRealm.close"), e); // Just log it here
391: }
392:
393: }
394:
395: /**
396: * Open the specified database connection.
397: *
398: * @return Connection to the database
399: */
400: protected Connection open() {
401:
402: try {
403: Context context = null;
404: if (localDataSource) {
405: context = ContextBindings.getClassLoader();
406: context = (Context) context.lookup("comp/env");
407: } else {
408: StandardServer server = (StandardServer) ServerFactory
409: .getServer();
410: context = server.getGlobalNamingContext();
411: }
412: DataSource dataSource = (DataSource) context
413: .lookup(dataSourceName);
414: return dataSource.getConnection();
415: } catch (Exception e) {
416: // Log the problem for posterity
417: log(sm.getString("dataSourceRealm.exception"), e);
418: }
419: return null;
420: }
421:
422: /**
423: * Return a PreparedStatement configured to perform the SELECT required
424: * to retrieve user credentials for the specified username.
425: *
426: * @param dbConnection The database connection to be used
427: * @param username Username for which credentials should be retrieved
428: *
429: * @exception SQLException if a database error occurs
430: */
431: private PreparedStatement credentials(Connection dbConnection,
432: String username) throws SQLException {
433:
434: PreparedStatement credentials = dbConnection
435: .prepareStatement(preparedCredentials.toString());
436:
437: credentials.setString(1, username);
438: return (credentials);
439:
440: }
441:
442: /**
443: * Return a short name for this Realm implementation.
444: */
445: protected String getName() {
446:
447: return (name);
448:
449: }
450:
451: /**
452: * Return the password associated with the given principal's user name.
453: */
454: protected String getPassword(String username) {
455:
456: return (null);
457:
458: }
459:
460: /**
461: * Return the Principal associated with the given user name.
462: */
463: protected Principal getPrincipal(String username) {
464:
465: return (null);
466:
467: }
468:
469: /**
470: * Return a PreparedStatement configured to perform the SELECT required
471: * to retrieve user roles for the specified username.
472: *
473: * @param dbConnection The database connection to be used
474: * @param username Username for which roles should be retrieved
475: *
476: * @exception SQLException if a database error occurs
477: */
478: private PreparedStatement roles(Connection dbConnection,
479: String username) throws SQLException {
480:
481: PreparedStatement roles = dbConnection
482: .prepareStatement(preparedRoles.toString());
483:
484: roles.setString(1, username);
485: return (roles);
486:
487: }
488:
489: // ------------------------------------------------------ Lifecycle Methods
490:
491: /**
492: *
493: * Prepare for active use of the public methods of this Component.
494: *
495: * @exception LifecycleException if this component detects a fatal error
496: * that prevents it from being started
497: */
498: public void start() throws LifecycleException {
499:
500: // Create the roles PreparedStatement string
501: preparedRoles = new StringBuffer("SELECT ");
502: preparedRoles.append(roleNameCol);
503: preparedRoles.append(" FROM ");
504: preparedRoles.append(userRoleTable);
505: preparedRoles.append(" WHERE ");
506: preparedRoles.append(userNameCol);
507: preparedRoles.append(" = ?");
508:
509: // Create the credentials PreparedStatement string
510: preparedCredentials = new StringBuffer("SELECT ");
511: preparedCredentials.append(userCredCol);
512: preparedCredentials.append(" FROM ");
513: preparedCredentials.append(userTable);
514: preparedCredentials.append(" WHERE ");
515: preparedCredentials.append(userNameCol);
516: preparedCredentials.append(" = ?");
517:
518: // Perform normal superclass initialization
519: super .start();
520:
521: }
522:
523: /**
524: * Gracefully shut down active use of the public methods of this Component.
525: *
526: * @exception LifecycleException if this component detects a fatal error
527: * that needs to be reported
528: */
529: public void stop() throws LifecycleException {
530:
531: // Perform normal superclass finalization
532: super.stop();
533:
534: }
535:
536: }
|