2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 * This document is a part of the source code and related artifacts
25 * for CollectionSpace, an open source collections management system
26 * for museums and related institutions:
28 * http://www.collectionspace.org
29 * http://wiki.collectionspace.org
31 * Copyright 2009 University of California at Berkeley
33 * Licensed under the Educational Community License (ECL), Version 2.0.
34 * You may not use this file except in compliance with this License.
36 * You may obtain a copy of the ECL 2.0 License at
38 * https://source.collectionspace.org/collection-space/LICENSE.txt
40 * Unless required by applicable law or agreed to in writing, software
41 * distributed under the License is distributed on an "AS IS" BASIS,
42 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43 * See the License for the specific language governing permissions and
44 * limitations under the License.
47 * To change this template, choose Tools | Templates
48 * and open the template in the editor.
50 package org.collectionspace.authentication.realm.db;
52 import java.lang.reflect.Constructor;
53 import java.security.Principal;
54 import java.security.acl.Group;
55 import java.sql.Connection;
56 import java.sql.PreparedStatement;
57 import java.sql.ResultSet;
58 import java.sql.SQLException;
59 import java.util.Collection;
60 import java.util.HashMap;
63 import javax.naming.Context;
64 import javax.naming.InitialContext;
65 import javax.naming.NamingException;
66 import javax.security.auth.login.FailedLoginException;
67 import javax.security.auth.login.LoginException;
68 import javax.sql.DataSource;
70 //import org.apache.commons.logging.Log;
71 //import org.apache.commons.logging.LogFactory;
73 import org.collectionspace.authentication.AuthN;
74 import org.collectionspace.authentication.CSpaceTenant;
75 import org.collectionspace.authentication.realm.CSpaceRealm;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
81 * CSpaceDbRealm provides access to user, password, role, tenant database
84 public class CSpaceDbRealm implements CSpaceRealm {
86 private Logger logger = LoggerFactory.getLogger(CSpaceDbRealm.class);
88 private String datasourceName;
89 private String principalsQuery;
90 private String rolesQuery;
91 private String tenantsQuery;
92 private boolean suspendResume;
95 * CSpace Database Realm
96 * @param datasourceName datasource name
98 public CSpaceDbRealm(Map options) {
99 datasourceName = (String) options.get("dsJndiName");
100 if (datasourceName == null) {
101 datasourceName = "java:/DefaultDS";
103 Object tmp = options.get("principalsQuery");
105 principalsQuery = tmp.toString();
107 tmp = options.get("rolesQuery");
109 rolesQuery = tmp.toString();
111 tmp = options.get("tenantsQuery");
113 tenantsQuery = tmp.toString();
115 tmp = options.get("suspendResume");
117 suspendResume = Boolean.valueOf(tmp.toString()).booleanValue();
119 if (logger.isTraceEnabled()) {
120 logger.trace("DatabaseServerLoginModule, dsJndiName=" + datasourceName);
121 logger.trace("principalsQuery=" + principalsQuery);
122 logger.trace("rolesQuery=" + rolesQuery);
123 logger.trace("suspendResume=" + suspendResume);
129 public String getUsersPassword(String username) throws LoginException {
131 String password = null;
132 Connection conn = null;
133 PreparedStatement ps = null;
136 conn = getConnection();
138 if (logger.isDebugEnabled()) {
139 logger.debug("Executing query: " + principalsQuery + ", with username: " + username);
141 ps = conn.prepareStatement(principalsQuery);
142 ps.setString(1, username);
143 rs = ps.executeQuery();
144 if (rs.next() == false) {
145 if (logger.isDebugEnabled()) {
146 logger.debug(principalsQuery + " returned no matches from db");
148 throw new FailedLoginException("No matching username found");
151 password = rs.getString(1);
152 } catch (SQLException ex) {
153 if (logger.isTraceEnabled() == true) {
154 logger.error("Could not open database to read AuthN tables.", ex);
156 LoginException le = new LoginException("Authentication query failed: " + ex.getLocalizedMessage());
159 } catch (Exception ex) {
160 LoginException le = new LoginException("Unknown Exception");
167 } catch (SQLException e) {
173 } catch (SQLException e) {
179 } catch (SQLException ex) {
187 * Execute the rolesQuery against the datasourceName to obtain the roles for
188 * the authenticated user.
189 * @return collection containing the roles
192 public Collection<Group> getRoles(String username, String principalClassName, String groupClassName) throws LoginException {
194 if (logger.isDebugEnabled()) {
195 logger.debug("getRoleSets using rolesQuery: " + rolesQuery + ", username: " + username);
198 Connection conn = null;
199 HashMap<String, Group> groupsMap = new HashMap<String, Group>();
200 PreparedStatement ps = null;
204 conn = getConnection();
205 // Get the user role names
206 if (logger.isDebugEnabled()) {
207 logger.debug("Executing query: " + rolesQuery + ", with username: " + username);
210 ps = conn.prepareStatement(rolesQuery);
212 ps.setString(1, username);
213 } catch (ArrayIndexOutOfBoundsException ignore) {
214 // The query may not have any parameters so just try it
216 rs = ps.executeQuery();
217 if (rs.next() == false) {
218 if (logger.isDebugEnabled()) {
219 logger.debug("No roles found");
221 // if(aslm.getUnauthenticatedIdentity() == null){
222 // throw new FailedLoginException("No matching username found in Roles");
224 /* We are running with an unauthenticatedIdentity so create an
225 empty Roles set and return.
228 Group g = createGroup(groupClassName, "Roles");
229 groupsMap.put(g.getName(), g);
230 return groupsMap.values();
234 String roleName = rs.getString(1);
235 String groupName = rs.getString(2);
236 if (groupName == null || groupName.length() == 0) {
240 Group group = (Group) groupsMap.get(groupName);
242 group = createGroup(groupClassName, groupName);
243 groupsMap.put(groupName, group);
247 Principal p = createPrincipal(principalClassName, roleName);
248 if (logger.isDebugEnabled()) {
249 logger.debug("Assign user to role " + roleName);
253 } catch (Exception e) {
254 logger.error("Failed to create principal: " + roleName + " " + e.toString());
258 } catch (SQLException ex) {
259 LoginException le = new LoginException("Query failed");
262 } catch (Exception e) {
263 LoginException le = new LoginException("unknown exception");
270 } catch (SQLException e) {
276 } catch (SQLException e) {
282 } catch (Exception ex) {
288 return groupsMap.values();
293 * Execute the tenantsQuery against the datasourceName to obtain the tenants for
294 * the authenticated user.
295 * @return collection containing the roles
298 public Collection<Group> getTenants(String username, String groupClassName) throws LoginException {
300 if (logger.isDebugEnabled()) {
301 logger.debug("getTenants using tenantsQuery: " + tenantsQuery + ", username: " + username);
304 Connection conn = null;
305 HashMap<String, Group> groupsMap = new HashMap<String, Group>();
306 PreparedStatement ps = null;
310 conn = getConnection();
311 // Get the user role names
312 if (logger.isDebugEnabled()) {
313 logger.debug("Executing query: " + tenantsQuery + ", with username: " + username);
316 ps = conn.prepareStatement(tenantsQuery);
318 ps.setString(1, username);
319 } catch (ArrayIndexOutOfBoundsException ignore) {
320 // The query may not have any parameters so just try it
322 rs = ps.executeQuery();
323 if (rs.next() == false) {
324 if (logger.isDebugEnabled()) {
325 logger.debug("No tenants found");
327 // We are running with an unauthenticatedIdentity so create an
328 // empty Tenants set and return.
329 // FIXME should this be allowed?
330 Group g = createGroup(groupClassName, "Tenants");
331 groupsMap.put(g.getName(), g);
332 return groupsMap.values();
336 String tenantId = rs.getString(1);
337 String tenantName = rs.getString(2);
338 String groupName = rs.getString(3);
339 if (groupName == null || groupName.length() == 0) {
340 groupName = "Tenants";
343 Group group = (Group) groupsMap.get(groupName);
345 group = createGroup(groupClassName, groupName);
346 groupsMap.put(groupName, group);
350 Principal p = createTenant(tenantName, tenantId);
351 if (logger.isDebugEnabled()) {
352 logger.debug("Assign user to tenant " + tenantName);
356 } catch (Exception e) {
357 logger.error("Failed to create tenant: " + tenantName + " " + e.toString());
360 } catch (SQLException ex) {
361 LoginException le = new LoginException("Query failed");
364 } catch (Exception e) {
365 LoginException le = new LoginException("unknown exception");
372 } catch (SQLException e) {
378 } catch (SQLException e) {
384 } catch (Exception ex) {
390 return groupsMap.values();
393 private CSpaceTenant createTenant(String name, String id) throws Exception {
394 return new CSpaceTenant(name, id);
397 private Group createGroup(String groupClassName, String name) throws Exception {
398 return (Group) createPrincipal(groupClassName, name);
401 private Principal createPrincipal(String principalClassName, String name) throws Exception {
402 ClassLoader loader = Thread.currentThread().getContextClassLoader();
403 Class clazz = loader.loadClass(principalClassName);
404 Class[] ctorSig = {String.class};
405 Constructor ctor = clazz.getConstructor(ctorSig);
406 Object[] ctorArgs = {name};
407 Principal p = (Principal) ctor.newInstance(ctorArgs);
411 private Connection getConnection() throws LoginException, SQLException {
412 InitialContext ctx = null;
413 Connection conn = null;
414 String dataSourceName = getDataSourceName();
415 DataSource ds = null;
417 ctx = new InitialContext();
419 ds = (DataSource) ctx.lookup(dataSourceName);
420 } catch (Exception e) {}
423 Context envCtx = (Context) ctx.lookup("java:comp/env");
424 ds = (DataSource) envCtx.lookup(dataSourceName);
425 } catch (Exception e) {}
428 Context envCtx = (Context) ctx.lookup("java:comp");
429 ds = (DataSource) envCtx.lookup(dataSourceName);
430 } catch (Exception e) {}
433 Context envCtx = (Context) ctx.lookup("java:");
434 ds = (DataSource) envCtx.lookup(dataSourceName);
435 } catch (Exception e) {}
438 Context envCtx = (Context) ctx.lookup("java");
439 ds = (DataSource) envCtx.lookup(dataSourceName);
440 } catch (Exception e) {}
443 ds = (DataSource) ctx.lookup("java:/" + dataSourceName);
444 } catch (Exception e) {}
447 ds = AuthN.getDataSource();
451 throw new IllegalArgumentException("datasource not found: " + dataSourceName);
454 conn = ds.getConnection();
456 conn = AuthN.getDataSource().getConnection(); //FIXME:REM - This is the result of some type of JNDI mess. Should try to solve this problem and clean up this code.
459 } catch (NamingException ex) {
460 LoginException le = new LoginException("Error looking up DataSource from: " + dataSourceName);
467 } catch (Exception e) {
476 * @return the datasourceName
478 public String getDataSourceName() {
479 return datasourceName;
483 * @return the principalQuery
485 public String getPrincipalQuery() {
486 return principalsQuery;
490 * @param principalQuery the principalQuery to set
492 public void setPrincipalQuery(String principalQuery) {
493 this.principalsQuery = principalQuery;
497 * @return the roleQuery
499 public String getRoleQuery() {
504 * @param roleQuery the roleQuery to set
506 public void setRoleQuery(String roleQuery) {
507 this.rolesQuery = roleQuery;
511 * @return the tenantQuery
513 public String getTenantQuery() {
518 * @param tenantQuery the tenantQuery to set
520 public void setTenantQuery(String tenantQuery) {
521 this.tenantsQuery = tenantQuery;