1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.security.auth.spi;
23
24 import java.security.acl.Group;
25 import java.sql.Connection;
26 import java.sql.PreparedStatement;
27 import java.sql.ResultSet;
28 import java.sql.SQLException;
29 import java.util.Map;
30
31 import javax.naming.InitialContext;
32 import javax.naming.NamingException;
33 import javax.security.auth.Subject;
34 import javax.security.auth.callback.CallbackHandler;
35 import javax.security.auth.login.FailedLoginException;
36 import javax.security.auth.login.LoginException;
37 import javax.sql.DataSource;
38 import javax.transaction.SystemException;
39 import javax.transaction.Transaction;
40 import javax.transaction.TransactionManager;
41
42 import org.jboss.security.plugins.TransactionManagerLocator;
43
44
45 /**
46 * A JDBC based login module that supports authentication and role mapping.
47 * It is based on two logical tables:
48 * <ul>
49 * <li>Principals(PrincipalID text, Password text)
50 * <li>Roles(PrincipalID text, Role text, RoleGroup text)
51 * </ul>
52 * <p>
53 * LoginModule options:
54 * <ul>
55 * <li><em>dsJndiName</em>: The name of the DataSource of the database
56 * containing the Principals, Roles tables
57 * <li><em>principalsQuery</em>: The prepared statement query, equivalent to:
58 * <pre>
59 * "select Password from Principals where PrincipalID=?"
60 * </pre>
61 * <li><em>rolesQuery</em>: The prepared statement query, equivalent to:
62 * <pre>
63 * "select Role, RoleGroup from Roles where PrincipalID=?"
64 * </pre>
65 * </ul>
66 *
67 * @author <a href="mailto:on@ibis.odessa.ua">Oleg Nitz</a>
68 * @author Scott.Stark@jboss.org
69 * @version $Revision: 86122 $
70 */
71 public class DatabaseServerLoginModule extends UsernamePasswordLoginModule
72 {
73 /** The JNDI name of the DataSource to use */
74 protected String dsJndiName;
75 /** The sql query to obtain the user password */
76 protected String principalsQuery = "select Password from Principals where PrincipalID=?";
77 /** The sql query to obtain the user roles */
78 protected String rolesQuery = "select Role, RoleGroup from Roles where PrincipalID=?";
79 /** Whether to suspend resume transactions during database operations */
80 protected boolean suspendResume = true;
81
82 protected String TX_MGR_JNDI_NAME = "java:/TransactionManager";
83
84 protected TransactionManager tm = null;
85
86 /**
87 * Initialize this LoginModule.
88 *
89 * @param options -
90 * dsJndiName: The name of the DataSource of the database containing the
91 * Principals, Roles tables
92 * principalsQuery: The prepared statement query, equivalent to:
93 * "select Password from Principals where PrincipalID=?"
94 * rolesQuery: The prepared statement query, equivalent to:
95 * "select Role, RoleGroup from Roles where PrincipalID=?"
96 */
97 public void initialize(Subject subject, CallbackHandler callbackHandler,
98 Map<String,?> sharedState, Map<String,?> options)
99 {
100 super.initialize(subject, callbackHandler, sharedState, options);
101 dsJndiName = (String) options.get("dsJndiName");
102 if( dsJndiName == null )
103 dsJndiName = "java:/DefaultDS";
104 Object tmp = options.get("principalsQuery");
105 if( tmp != null )
106 principalsQuery = tmp.toString();
107 tmp = options.get("rolesQuery");
108 if( tmp != null )
109 rolesQuery = tmp.toString();
110 tmp = options.get("suspendResume");
111 if( tmp != null )
112 suspendResume = Boolean.valueOf(tmp.toString()).booleanValue();
113 if (log.isTraceEnabled())
114 {
115 log.trace("DatabaseServerLoginModule, dsJndiName="+dsJndiName);
116 log.trace("principalsQuery="+principalsQuery);
117 log.trace("rolesQuery="+rolesQuery);
118 log.trace("suspendResume="+suspendResume);
119 }
120 //Get the Transaction Manager JNDI Name
121 String jname = (String) options.get("transactionManagerJndiName");
122 if(jname != null)
123 this.TX_MGR_JNDI_NAME = jname;
124
125 try
126 {
127 if(this.suspendResume)
128 tm = this.getTransactionManager();
129 }
130 catch (NamingException e)
131 {
132 throw new RuntimeException("Unable to get Transaction Manager", e);
133 }
134 }
135
136 /** Get the expected password for the current username available via
137 * the getUsername() method. This is called from within the login()
138 * method after the CallbackHandler has returned the username and
139 * candidate password.
140 * @return the valid password String
141 */
142 protected String getUsersPassword() throws LoginException
143 {
144 boolean trace = log.isTraceEnabled();
145 String username = getUsername();
146 String password = null;
147 Connection conn = null;
148 PreparedStatement ps = null;
149 ResultSet rs = null;
150
151 Transaction tx = null;
152 if (suspendResume)
153 {
154 //tx = TransactionDemarcationSupport.suspendAnyTransaction();
155 try
156 {
157 if(tm == null)
158 throw new IllegalStateException("Transaction Manager is null");
159 tx = tm.suspend();
160 }
161 catch (SystemException e)
162 {
163 throw new RuntimeException(e);
164 }
165 if (trace)
166 log.trace("suspendAnyTransaction");
167 }
168
169 try
170 {
171 InitialContext ctx = new InitialContext();
172 DataSource ds = (DataSource) ctx.lookup(dsJndiName);
173 conn = ds.getConnection();
174 // Get the password
175 if (trace)
176 log.trace("Excuting query: "+principalsQuery+", with username: "+username);
177 ps = conn.prepareStatement(principalsQuery);
178 ps.setString(1, username);
179 rs = ps.executeQuery();
180 if( rs.next() == false )
181 {
182 if(trace)
183 log.trace("Query returned no matches from db");
184 throw new FailedLoginException("No matching username found in Principals");
185 }
186
187 password = rs.getString(1);
188 password = convertRawPassword(password);
189 if(trace)
190 log.trace("Obtained user password");
191 }
192 catch(NamingException ex)
193 {
194 LoginException le = new LoginException("Error looking up DataSource from: "+dsJndiName);
195 le.initCause(ex);
196 throw le;
197 }
198 catch(SQLException ex)
199 {
200 LoginException le = new LoginException("Query failed");
201 le.initCause(ex);
202 throw le;
203 }
204 finally
205 {
206 if (rs != null)
207 {
208 try
209 {
210 rs.close();
211 }
212 catch(SQLException e)
213 {}
214 }
215 if( ps != null )
216 {
217 try
218 {
219 ps.close();
220 }
221 catch(SQLException e)
222 {}
223 }
224 if( conn != null )
225 {
226 try
227 {
228 conn.close();
229 }
230 catch (SQLException ex)
231 {}
232 }
233 if (suspendResume)
234 {
235 //TransactionDemarcationSupport.resumeAnyTransaction(tx);
236 try
237 {
238 tm.resume(tx);
239 }
240 catch (Exception e)
241 {
242 throw new RuntimeException(e);
243 }
244 if (log.isTraceEnabled())
245 log.trace("resumeAnyTransaction");
246 }
247 }
248 return password;
249 }
250
251 /** Execute the rolesQuery against the dsJndiName to obtain the roles for
252 the authenticated user.
253
254 @return Group[] containing the sets of roles
255 */
256 protected Group[] getRoleSets() throws LoginException
257 {
258 String username = getUsername();
259 if (log.isTraceEnabled())
260 log.trace("getRoleSets using rolesQuery: "+rolesQuery+", username: "+username);
261 Group[] roleSets = Util.getRoleSets(username, dsJndiName, rolesQuery, this,
262 suspendResume);
263 return roleSets;
264 }
265
266 /** A hook to allow subclasses to convert a password from the database
267 into a plain text string or whatever form is used for matching against
268 the user input. It is called from within the getUsersPassword() method.
269 @param rawPassword - the password as obtained from the database
270 @return the argument rawPassword
271 */
272 protected String convertRawPassword(String rawPassword)
273 {
274 return rawPassword;
275 }
276
277 protected TransactionManager getTransactionManager() throws NamingException
278 {
279 TransactionManagerLocator tml = new TransactionManagerLocator();
280 return tml.getTM(this.TX_MGR_JNDI_NAME);
281 }
282 }