001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.server.security;
030:
031: import com.caucho.config.*;
032: import com.caucho.config.types.Period;
033: import com.caucho.server.connection.CauchoRequest;
034: import com.caucho.server.dispatch.ServletConfigException;
035: import com.caucho.server.session.SessionManager;
036: import com.caucho.server.webapp.Application;
037: import com.caucho.util.CharBuffer;
038: import com.caucho.util.L10N;
039:
040: import javax.annotation.PostConstruct;
041: import javax.naming.Context;
042: import javax.naming.InitialContext;
043: import javax.servlet.ServletContext;
044: import javax.servlet.ServletException;
045: import javax.servlet.http.Cookie;
046: import javax.servlet.http.HttpServletRequest;
047: import javax.servlet.http.HttpServletResponse;
048: import javax.sql.DataSource;
049: import java.security.Principal;
050: import java.sql.Connection;
051: import java.sql.PreparedStatement;
052: import java.sql.ResultSet;
053: import java.sql.SQLException;
054: import java.util.logging.Level;
055: import java.util.logging.Logger;
056:
057: /**
058: * An authenticator using JDBC.
059: *
060: * <p>The default table schema looks something like:
061: * <pre>
062: * CREATE TABLE LOGIN (
063: * username VARCHAR(250) NOT NULL,
064: * password VARCHAR(250),
065: * cookie VARCHAR(250),
066: * PRIMARY KEY (username)
067: * );
068: * </pre>
069: *
070: * <code><pre>
071: * <authenticator url="jdbc:database=jdbc/user">
072: * </authenticator>
073: * </pre></code>
074: */
075:
076: public class JdbcAuthenticator extends AbstractAuthenticator {
077: private static final Logger log = Logger
078: .getLogger(JdbcAuthenticator.class.getName());
079: private static final L10N L = new L10N(JdbcAuthenticator.class);
080:
081: private DataSource _dataSource;
082:
083: private String _passwordQuery = "SELECT password FROM LOGIN WHERE username=?";
084:
085: private String _cookieUpdate = "UPDATE LOGIN SET cookie=? WHERE username=?";
086:
087: private String _cookieQuery = "SELECT username FROM LOGIN where cookie=?";
088: private boolean _cookieLogout;
089:
090: private String _roleQuery;
091:
092: protected boolean _useCookie;
093: protected int _cookieVersion = -1;
094: protected String _cookieDomain;
095: protected long _cookieMaxAge = 365L * 24L * 3600L * 1000L;
096:
097: private CharBuffer _cb = new CharBuffer();
098:
099: /**
100: * Gets the database
101: */
102: public DataSource getDataSource() {
103: return _dataSource;
104: }
105:
106: /**
107: * Sets the database pool name.
108: */
109: public void setDataSource(DataSource dataSource) {
110: _dataSource = dataSource;
111: }
112:
113: /**
114: * Gets the password query.
115: *
116: * <p>Example:
117: * <pre><code>
118: * SELECT password FROM LOGIN WHERE username=?
119: * </code></pre>
120: */
121: public String getPasswordQuery() {
122: return _passwordQuery;
123: }
124:
125: /**
126: * Sets the password query.
127: */
128: public void setPasswordQuery(String query) {
129: _passwordQuery = query;
130: }
131:
132: /**
133: * Gets the cookie auth query.
134: */
135: public String getCookieAuthQuery() {
136: return _cookieQuery;
137: }
138:
139: /**
140: * Sets the cookie auth query.
141: */
142: public void setCookieAuthQuery(String query) {
143: _cookieQuery = query;
144: }
145:
146: /**
147: * Gets the cookie update query.
148: */
149: public String getCookieAuthUpdate() {
150: return _cookieUpdate;
151: }
152:
153: /**
154: * Sets the cookie update query.
155: */
156: public void setCookieAuthUpdate(String query) {
157: _cookieUpdate = query;
158: }
159:
160: /**
161: * If true, the cookie is removed on logout
162: */
163: public void setCookieLogout(boolean cookieLogout) {
164: _cookieLogout = cookieLogout;
165: }
166:
167: /**
168: * Gets the role query.
169: */
170: public String getRoleQuery() {
171: return _roleQuery;
172: }
173:
174: /**
175: * Sets the role query.
176: */
177: public void setRoleQuery(String query) {
178: _roleQuery = query;
179: }
180:
181: /**
182: * Returns true if Resin should generate the resinauth cookie by default.
183: */
184: public boolean getUseCookie() {
185: return _useCookie;
186: }
187:
188: /**
189: * Set true if Resin should generate the resinauth cookie by default.
190: */
191: public void setUseCookie(boolean useCookie) {
192: _useCookie = useCookie;
193: }
194:
195: /**
196: * Returns the version for a login cookie.
197: */
198: public int getCookieVersion() {
199: return _cookieVersion;
200: }
201:
202: /**
203: * Sets the version for a login cookie.
204: */
205: public void setCookieVersion(int version) {
206: _cookieVersion = version;
207: }
208:
209: /**
210: * Returns the domain for a login cookie.
211: */
212: public String getCookieDomain() {
213: return _cookieDomain;
214: }
215:
216: /**
217: * Sets the domain for a login cookie.
218: */
219: public void setCookieDomain(String cookieDomain) {
220: _cookieDomain = cookieDomain;
221: }
222:
223: /**
224: * Returns the max-age for a login cookie.
225: */
226: public long getCookieMaxAge() {
227: return _cookieMaxAge;
228: }
229:
230: /**
231: * Sets the max age for a login cookie.
232: */
233: public void setCookieMaxAge(Period cookieMaxAge) {
234: _cookieMaxAge = cookieMaxAge.getPeriod();
235: }
236:
237: /**
238: * Initialize the authenticator.
239: */
240: @PostConstruct
241: public void init() throws ServletException {
242: super .init();
243:
244: if (_dataSource == null) {
245: try {
246: Context ic = new InitialContext();
247:
248: _dataSource = (DataSource) ic
249: .lookup("java:comp/env/jdbc/db-pool");
250: } catch (Exception e) {
251: log.log(Level.FINE, e.toString(), e);
252: }
253:
254: if (_dataSource == null)
255: throw new ServletConfigException(L
256: .l("Unknown database pool jdbc/db-pool."));
257: }
258:
259: int i = _passwordQuery.indexOf('?');
260: if (i < 0)
261: throw new ConfigException(L
262: .l("'password-query' expects a parameter"));
263:
264: if (_cookieQuery != null) {
265: i = _cookieQuery.indexOf('?');
266: if (i < 0)
267: throw new ConfigException(L
268: .l("'cookie-auth-query' expects a parameter"));
269: }
270:
271: if (_cookieUpdate != null) {
272: i = _cookieUpdate.indexOf('?');
273: if (i < 0)
274: throw new ConfigException(
275: L
276: .l("'cookie-auth-update' expects two parameters"));
277: int j = _cookieUpdate.indexOf('?', i + 1);
278: if (j < 0)
279: throw new ConfigException(
280: L
281: .l("'cookie-auth-update' expects two parameters"));
282: }
283:
284: if ((_cookieUpdate != null) && (_cookieQuery == null))
285: throw new ServletConfigException(L.l("<{0}> expects `{1}'",
286: "cookie-auth-update", "cookie-query"));
287:
288: if (_roleQuery != null) {
289: i = _roleQuery.indexOf('?');
290: if (i < 0)
291: throw new ConfigException(L
292: .l("'role-query' expects a parameter"));
293: }
294: }
295:
296: /**
297: * Authenticates the user given the request.
298: *
299: * @param username the user name for the login
300: * @param password the password for the login
301: *
302: * @return the authenticated user or null for a failure
303: */
304: public Principal loginImpl(HttpServletRequest request,
305: HttpServletResponse response, ServletContext application,
306: String username, String password) throws ServletException {
307: Principal user = loginImpl(username, password);
308:
309: if (_cookieQuery == null || user == null)
310: return user;
311:
312: String cookieAuth = (String) request
313: .getAttribute("j_use_cookie_auth");
314: if (cookieAuth == null)
315: cookieAuth = (String) request
316: .getParameter("j_use_cookie_auth");
317:
318: if ("true".equals(cookieAuth) || "on".equals(cookieAuth)
319: || _useCookie && cookieAuth == null)
320: addAuthCookie(request, response, application, user);
321:
322: return user;
323: }
324:
325: /**
326: * Adds a cookie to store authentication.
327: */
328: protected void addAuthCookie(HttpServletRequest request,
329: HttpServletResponse response, ServletContext application,
330: Principal user)
331:
332: {
333: Application app = (Application) application;
334: SessionManager sm = app.getSessionManager();
335: String id;
336:
337: id = sm.createSessionId(request);
338:
339: if (updateCookie(user, id)) {
340: Cookie cookie = new Cookie("resinauthid", id);
341: cookie.setPath("/");
342:
343: if (getCookieVersion() >= 0)
344: cookie.setVersion(getCookieVersion());
345: else
346: cookie.setVersion(sm.getCookieVersion());
347:
348: if (_cookieDomain != null)
349: cookie.setDomain(_cookieDomain);
350: else if (getCookieDomain() != null)
351: cookie.setDomain(getCookieDomain());
352: else
353: cookie.setDomain(sm.getCookieDomain());
354:
355: if (_cookieMaxAge > 0)
356: cookie.setMaxAge((int) (_cookieMaxAge / 1000L));
357:
358: response.addCookie(cookie);
359: }
360: }
361:
362: /**
363: * Authenticates the user given the request.
364: *
365: * @param username the user name for the login
366: * @param password the password for the login
367: *
368: * @return the authenticated user or null for a failure
369: */
370: public Principal loginImpl(String username, String password)
371: throws ServletException {
372: Connection conn = null;
373: PreparedStatement stmt = null;
374: ResultSet rs = null;
375:
376: try {
377: conn = _dataSource.getConnection();
378: stmt = conn.prepareStatement(_passwordQuery);
379:
380: stmt.setString(1, username);
381:
382: rs = stmt.executeQuery();
383: if (!rs.next()) {
384: if (log.isLoggable(Level.FINE))
385: log.fine("no such user:" + username);
386:
387: return null;
388: }
389:
390: String dbPassword = rs.getString(1);
391:
392: if (dbPassword != null && dbPassword.equals(password)) {
393: return new CachingPrincipal(username);
394: } else {
395: if (log.isLoggable(Level.FINE))
396: log.fine("mismatched password:" + username);
397:
398: return null;
399: }
400: } catch (Exception e) {
401: e.printStackTrace();
402: throw new ServletException(e);
403: } finally {
404: try {
405: if (rs != null)
406: rs.close();
407: } catch (SQLException e) {
408: }
409: try {
410: if (stmt != null)
411: stmt.close();
412: } catch (SQLException e) {
413: }
414: try {
415: if (conn != null)
416: conn.close();
417: } catch (SQLException e) {
418: }
419: }
420: }
421:
422: /**
423: * Returns the password for authenticators too lazy to calculate the
424: * digest.
425: */
426: protected String getDigestPassword(HttpServletRequest request,
427: HttpServletResponse response, ServletContext application,
428: String username, String realm) throws ServletException {
429: Connection conn = null;
430: PreparedStatement stmt = null;
431: ResultSet rs = null;
432:
433: try {
434: conn = _dataSource.getConnection();
435: stmt = conn.prepareStatement(_passwordQuery);
436:
437: stmt.setString(1, username);
438:
439: rs = stmt.executeQuery();
440: if (!rs.next()) {
441: if (log.isLoggable(Level.FINE))
442: log.fine("no such user:" + username);
443:
444: return null;
445: }
446:
447: String dbPassword = rs.getString(1);
448:
449: return dbPassword;
450: } catch (Exception e) {
451: throw new ServletException(e);
452: } finally {
453: try {
454: if (rs != null)
455: rs.close();
456: } catch (SQLException e) {
457: }
458: try {
459: if (stmt != null)
460: stmt.close();
461: } catch (SQLException e) {
462: }
463: try {
464: if (conn != null)
465: conn.close();
466: } catch (SQLException e) {
467: }
468: }
469: }
470:
471: protected Principal getUserPrincipalImpl(
472: HttpServletRequest request, ServletContext application)
473: throws ServletException {
474: if (_cookieQuery == null)
475: return null;
476:
477: Cookie cookie = null;
478:
479: if (request instanceof CauchoRequest)
480: cookie = ((CauchoRequest) request).getCookie("resinauthid");
481: else {
482: Cookie[] cookies = request.getCookies();
483: for (int i = 0; cookies != null && i < cookies.length; i++) {
484: if (cookies[i].getName().equals("resinauthid")) {
485: cookie = cookies[i];
486: break;
487: }
488: }
489: }
490:
491: if (cookie == null)
492: return null;
493:
494: return authenticateCookie(cookie.getValue());
495: }
496:
497: /**
498: * Authenticate based on a cookie.
499: *
500: * @param cookieValue the value of the resin-auth cookie
501: *
502: * @return the user for the cookie.
503: */
504: public Principal authenticateCookie(String cookieValue)
505: throws ServletException {
506: if (_cookieQuery == null)
507: return null;
508:
509: Connection conn = null;
510: PreparedStatement stmt = null;
511: ResultSet rs = null;
512:
513: try {
514: conn = _dataSource.getConnection();
515: stmt = conn.prepareStatement(_cookieQuery);
516: stmt.setString(1, cookieValue);
517:
518: rs = stmt.executeQuery();
519: if (!rs.next())
520: return null;
521:
522: String user = rs.getString(1);
523:
524: if (user != null)
525: return new CachingPrincipal(user);
526: else
527: return null;
528: } catch (Exception e) {
529: throw new ServletException(e);
530: } finally {
531: try {
532: if (rs != null)
533: rs.close();
534: } catch (SQLException e) {
535: }
536: try {
537: if (stmt != null)
538: stmt.close();
539: } catch (SQLException e) {
540: }
541: try {
542: if (conn != null)
543: conn.close();
544: } catch (SQLException e) {
545: }
546: }
547: }
548:
549: /**
550: * Associates a user with a persistent cookie.
551: *
552: * @param user the user for the cookie
553: * @param cookieValue the value of the resin-auth cookie
554: *
555: * @return true if the cookie value is valid, i.e. it's unique
556: */
557: public boolean updateCookie(Principal user, String cookieValue) {
558: if (_cookieUpdate == null || user == null
559: || cookieValue == null)
560: return true;
561:
562: Connection conn = null;
563: PreparedStatement stmt = null;
564:
565: try {
566: conn = _dataSource.getConnection();
567: stmt = conn.prepareStatement(_cookieUpdate);
568: stmt.setString(1, cookieValue);
569: stmt.setString(2, user.getName());
570:
571: stmt.executeUpdate();
572: } catch (Exception e) {
573: log.log(Level.FINE, e.toString(), e);
574: } finally {
575: try {
576: if (stmt != null)
577: stmt.close();
578: } catch (SQLException e) {
579: }
580: try {
581: if (conn != null)
582: conn.close();
583: } catch (SQLException e) {
584: }
585: }
586:
587: return true;
588: }
589:
590: public boolean isUserInRole(HttpServletRequest request,
591: HttpServletResponse response, ServletContext application,
592: Principal principal, String role) {
593: if (_roleQuery == null)
594: return principal != null && "user".equals(role);
595: else if (principal == null || role == null)
596: return false;
597:
598: CachingPrincipal cachingPrincipal = null;
599:
600: if (principal instanceof CachingPrincipal) {
601: cachingPrincipal = (CachingPrincipal) principal;
602:
603: Boolean isInRole = cachingPrincipal.isInRole(role);
604:
605: if (isInRole != null)
606: return isInRole.equals(Boolean.TRUE);
607: }
608:
609: Connection conn = null;
610: PreparedStatement stmt = null;
611: ResultSet rs = null;
612:
613: try {
614: conn = _dataSource.getConnection();
615: stmt = conn.prepareStatement(_roleQuery);
616: stmt.setString(1, principal.getName());
617:
618: boolean inRole = false;
619:
620: rs = stmt.executeQuery();
621: while (rs.next()) {
622: String dbRole = rs.getString(1);
623:
624: if (cachingPrincipal != null)
625: cachingPrincipal.addRole(dbRole);
626:
627: if (role.equals(dbRole))
628: inRole = true;
629: }
630:
631: return inRole;
632: } catch (Exception e) {
633: log.log(Level.FINE, e.toString(), e);
634:
635: return false;
636: } finally {
637: try {
638: if (rs != null)
639: rs.close();
640: } catch (SQLException e) {
641: }
642: try {
643: if (stmt != null)
644: stmt.close();
645: } catch (SQLException e) {
646: }
647: try {
648: if (conn != null)
649: conn.close();
650: } catch (SQLException e) {
651: }
652: }
653: }
654:
655: /**
656: * Logs the user out from the session.
657: *
658: * @param request the servlet request
659: */
660: public void logout(HttpServletRequest request,
661: HttpServletResponse response, ServletContext application,
662: Principal user) throws ServletException {
663: super .logout(request, response, application, user);
664:
665: // null the cookie
666: if (_cookieLogout)
667: updateCookie(user, "");
668: }
669: }
|