/* * $Id$ * * Authors: * Jeff Buchbinder <jeff@freemedsoftware.org> * * REMITT Electronic Medical Information Translation and Transmission * Copyright (C) 1999-2014 FreeMED Software Foundation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.remitt.server.jaas; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Serializable; import java.security.Principal; import java.security.acl.Group; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; import javax.servlet.ServletContext; import org.remitt.server.DbUtil; public class HttpLoginModule implements LoginModule { public class MyPrincipal implements Principal, Serializable { private static final long serialVersionUID = -9015495525770274310L; private final String name; public MyPrincipal(String name) { this.name = name; } public String getName() { return name; } public boolean equals(Object o) { if (!(o instanceof MyPrincipal)) return false; if (((MyPrincipal) o).getName().compareTo(this.getName()) == 0) return true; return false; } } public class MyGroup implements Group, Serializable { private static final long serialVersionUID = 1L; private final String name; private final Set<Principal> users = new HashSet<Principal>(); public MyGroup(String name) { this.name = name; } public boolean addMember(Principal user) { return users.add(user); } public boolean removeMember(Principal user) { return users.remove(user); } public boolean isMember(Principal member) { return users.contains(member); } public Enumeration<? extends Principal> members() { return Collections.enumeration(users); } public String getName() { return name; } public boolean equals(Object o) { if (!(o instanceof MyGroup)) return false; if (((MyGroup) o).getName().compareTo(this.getName()) == 0) return true; return false; } } private Subject subject; private CallbackHandler callbackHandler; @SuppressWarnings("unused") private Map<String, ?> sharedState = null; @SuppressWarnings("unused") private Map<String, ?> options = null; @SuppressWarnings("unused") private boolean commitSucceeded = false; private boolean loginSucceeded = false; private String username; private Principal user; private Principal[] roles; public static Properties config = null; @Override public boolean abort() throws LoginException { return false; } @Override public boolean commit() throws LoginException { System.out.println("HttpLoginModule commit()"); if (!loginSucceeded) { // We didn't authenticate the user, but someone else did. // Clean up our state, but don't add our principal to // the subject // username = null; // return false; } assignPrincipal(user); for (Principal role : roles) { assignPrincipal(role); } // Clean up our internal state username = null; commitSucceeded = true; for (Principal s : subject.getPrincipals()) { System.out .println("Found principal " + s.getName() + " in subject"); } return true; } /** * Assign a principal to the current <Subject>. * * @param p */ private void assignPrincipal(Principal p) { // Make sure we don't add duplicate principals if (!subject.getPrincipals().contains(p)) { subject.getPrincipals().add(p); } // System.out.println("Assigned principal " + p.getName() + " of type " // + p.getClass().getName() + " to user " + username); } @Override public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; } @Override public boolean login() throws LoginException { // System.out.println("login() called"); NameCallback nameCallback = new NameCallback("Username"); PasswordCallback passwordCallback = new PasswordCallback("Password", false); Callback[] callbacks = new Callback[] { nameCallback, passwordCallback }; try { callbackHandler.handle(callbacks); } catch (IOException e) { throw new LoginException(e.toString()); } catch (UnsupportedCallbackException e) { throw new LoginException(e.toString()); } username = nameCallback.getName(); char[] password = passwordCallback.getPassword(); passwordCallback.clearPassword(); // Setup mysql connection Connection c = getConnection(); boolean status = true; PreparedStatement cStmt = null; try { cStmt = c.prepareStatement("SELECT COUNT(*) AS c FROM tUser " + " WHERE username = ? AND passhash = MD5(?);"); cStmt.setString(1, username); cStmt.setString(2, new String(password)); cStmt.execute(); ResultSet rs = cStmt.getResultSet(); rs.next(); if (rs.getInt("c") < 1) { System.out.println("Was not able to login user " + username + ", no user with that password found"); status = false; } rs.close(); } catch (NullPointerException npe) { System.out.println("Caught NullPointerException: " + npe); status = false; } catch (SQLException e) { System.out.println("Caught SQLException: " + e); status = false; } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } // Bomb out if not validated if (!status) { return false; } // Validate user and password System.out.println("Appears to have validated properly."); user = new MyPrincipal(username); roles = getRolesForUser(username); return true; } @Override public boolean logout() throws LoginException { return false; } /** * Get list of <Principal> roles for the specified user. * * @param username * String name of the user. * @return Array of principal objects. */ public Principal[] getRolesForUser(String username) { List<Principal> ret = new ArrayList<Principal>(); // Setup mysql connection Connection c = getConnection(); PreparedStatement cStmt = null; try { cStmt = c.prepareStatement("SELECT rolename FROM tRole " + " WHERE username = ? ;"); cStmt.setString(1, username); if (cStmt.execute()) { ResultSet rs = cStmt.getResultSet(); while (rs.next()) { ret.add(new MyPrincipal(rs.getString(1))); } rs.close(); } } catch (NullPointerException npe) { System.out.println("Caught NullPointerException: " + npe); } catch (SQLException e) { System.out.println("Caught SQLException: " + e); } finally { DbUtil.closeSafely(cStmt); DbUtil.closeSafely(c); } return ret.toArray(new Principal[0]); } /** * Get servlet properties, with caching, respecting overrides. Is more or * less a reimplementation of org.remitt.server.Configuration and its * CompositeConfiguration object, except that we don't want the constraint * on the J2EE container of having to have all of the libraries to make that * work. * * @return Servlet properties. */ public Properties getProperties() { if (config == null) { System.out.println("HttpLoginModule: loading properties"); config = new Properties(); ServletContext ctx = SecurityFilter.getFilterConfig() .getServletContext(); Properties defaults = new Properties(); try { defaults.load(new FileInputStream(ctx .getRealPath("/WEB-INF/remitt.properties"))); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } // Copy defaults in Iterator<Object> dIter = defaults.keySet().iterator(); while (dIter.hasNext()) { Object item = dIter.next(); config.put(item, defaults.get(item)); } // Load all "override" properties, only if it exists if (System.getProperty("remitt.properties") != null) { Properties overrides = new Properties(); try { overrides.load(new FileInputStream(System .getProperty("remitt.properties"))); } catch (FileNotFoundException e) { System.out .println("getProperties(): no override file found"); } catch (IOException e) { e.printStackTrace(); } catch (NullPointerException e) { // Don't do anything, just don't load } finally { // Clobber defaults with overrides Iterator<Object> oIter = overrides.keySet().iterator(); while (oIter.hasNext()) { Object item = oIter.next(); config.put(item, overrides.get(item)); } } } } return config; } /** * Get a database connection for authentication. * * @return */ protected Connection getConnection() { Properties p = getProperties(); // Setup mysql connection Connection c = null; try { // Class.forName("com.mysql.jdbc.Driver").newInstance(); // c = DriverManager.getConnection("jdbc:mysql://localhost/remitt", // "remitt", "remitt"); Class.forName(p.getProperty("db.driver")).newInstance(); c = DriverManager.getConnection(p.getProperty("db.url")); // System.out.println("Connected to the database for auth"); } catch (Exception e) { e.printStackTrace(); return null; } return c; } }