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 tenantsQueryNoDisabled;
92 private String tenantsQueryWithDisabled;
93 private boolean suspendResume;
96 * CSpace Database Realm
97 * @param datasourceName datasource name
99 public CSpaceDbRealm(Map options) {
100 datasourceName = (String) options.get("dsJndiName");
101 if (datasourceName == null) {
102 datasourceName = "java:/DefaultDS";
104 Object tmp = options.get("principalsQuery");
106 principalsQuery = tmp.toString();
108 tmp = options.get("rolesQuery");
110 rolesQuery = tmp.toString();
112 tmp = options.get("tenantsQueryNoDisabled");
114 tenantsQueryNoDisabled = tmp.toString();
116 tmp = options.get("tenantsQueryWithDisabled");
118 tenantsQueryWithDisabled = tmp.toString();
120 tmp = options.get("suspendResume");
122 suspendResume = Boolean.valueOf(tmp.toString()).booleanValue();
124 if (logger.isTraceEnabled()) {
125 logger.trace("DatabaseServerLoginModule, dsJndiName=" + datasourceName);
126 logger.trace("principalsQuery=" + principalsQuery);
127 logger.trace("rolesQuery=" + rolesQuery);
128 logger.trace("suspendResume=" + suspendResume);
134 public String getUsersPassword(String username) throws LoginException {
136 String password = null;
137 Connection conn = null;
138 PreparedStatement ps = null;
141 conn = getConnection();
143 if (logger.isDebugEnabled()) {
144 logger.debug("Executing query: " + principalsQuery + ", with username: " + username);
146 ps = conn.prepareStatement(principalsQuery);
147 ps.setString(1, username);
148 rs = ps.executeQuery();
149 if (rs.next() == false) {
150 if (logger.isDebugEnabled()) {
151 logger.debug(principalsQuery + " returned no matches from db");
153 throw new FailedLoginException("No matching username found");
156 password = rs.getString(1);
157 } catch (SQLException ex) {
158 if (logger.isTraceEnabled() == true) {
159 logger.error("Could not open database to read AuthN tables.", ex);
161 LoginException le = new LoginException("Authentication query failed: " + ex.getLocalizedMessage());
164 } catch (Exception ex) {
165 LoginException le = new LoginException("Unknown Exception");
172 } catch (SQLException e) {
178 } catch (SQLException e) {
184 } catch (SQLException ex) {
192 * Execute the rolesQuery against the datasourceName to obtain the roles for
193 * the authenticated user.
194 * @return collection containing the roles
197 public Collection<Group> getRoles(String username, String principalClassName, String groupClassName) throws LoginException {
199 if (logger.isDebugEnabled()) {
200 logger.debug("getRoleSets using rolesQuery: " + rolesQuery + ", username: " + username);
203 Connection conn = null;
204 HashMap<String, Group> groupsMap = new HashMap<String, Group>();
205 PreparedStatement ps = null;
209 conn = getConnection();
210 // Get the user role names
211 if (logger.isDebugEnabled()) {
212 logger.debug("Executing query: " + rolesQuery + ", with username: " + username);
215 ps = conn.prepareStatement(rolesQuery);
217 ps.setString(1, username);
218 } catch (ArrayIndexOutOfBoundsException ignore) {
219 // The query may not have any parameters so just try it
221 rs = ps.executeQuery();
222 if (rs.next() == false) {
223 if (logger.isDebugEnabled()) {
224 logger.debug("No roles found");
226 // if(aslm.getUnauthenticatedIdentity() == null){
227 // throw new FailedLoginException("No matching username found in Roles");
229 /* We are running with an unauthenticatedIdentity so create an
230 empty Roles set and return.
233 Group g = createGroup(groupClassName, "Roles");
234 groupsMap.put(g.getName(), g);
235 return groupsMap.values();
239 String roleName = rs.getString(1);
240 String groupName = rs.getString(2);
241 if (groupName == null || groupName.length() == 0) {
245 Group group = (Group) groupsMap.get(groupName);
247 group = createGroup(groupClassName, groupName);
248 groupsMap.put(groupName, group);
252 Principal p = createPrincipal(principalClassName, roleName);
253 if (logger.isDebugEnabled()) {
254 logger.debug("Assign user to role " + roleName);
258 } catch (Exception e) {
259 logger.error("Failed to create principal: " + roleName + " " + e.toString());
263 } catch (SQLException ex) {
264 LoginException le = new LoginException("Query failed");
267 } catch (Exception e) {
268 LoginException le = new LoginException("unknown exception");
275 } catch (SQLException e) {
281 } catch (SQLException e) {
287 } catch (Exception ex) {
293 return groupsMap.values();
297 public Collection<Group> getTenants(String username, String groupClassName) throws LoginException {
298 return getTenants(username, groupClassName, false);
301 private boolean userIsTenantManager(Connection conn, String username) {
302 String acctQuery = "SELECT csid FROM accounts_common WHERE userid=?";
303 PreparedStatement ps = null;
305 boolean accountIsTenantManager = false;
307 ps = conn.prepareStatement(acctQuery);
308 ps.setString(1, username);
309 rs = ps.executeQuery();
311 String acctCSID = rs.getString(1);
312 if(AuthN.TENANT_MANAGER_ACCT_ID.equals(acctCSID)) {
313 accountIsTenantManager = true;
316 } catch (SQLException ex) {
317 if(logger.isDebugEnabled()) {
318 logger.debug("userIsTenantManager query failed on SQL error: " + ex.getLocalizedMessage());
320 } catch (Exception e) {
321 if(logger.isDebugEnabled()) {
322 logger.debug("userIsTenantManager unknown error: " + e.getLocalizedMessage());
328 } catch (SQLException e) {
334 } catch (SQLException e) {
338 return accountIsTenantManager;
342 * Execute the tenantsQuery against the datasourceName to obtain the tenants for
343 * the authenticated user.
344 * @return collection containing the roles
347 public Collection<Group> getTenants(String username, String groupClassName, boolean includeDisabledTenants) throws LoginException {
349 String tenantsQuery = getTenantQuery(includeDisabledTenants);
351 if (logger.isDebugEnabled()) {
352 logger.debug("getTenants using tenantsQuery: " + tenantsQuery + ", username: " + username);
355 Connection conn = null;
356 HashMap<String, Group> groupsMap = new HashMap<String, Group>();
357 PreparedStatement ps = null;
359 final String defaultGroupName = "Tenants";
362 conn = getConnection();
364 ps = conn.prepareStatement(tenantsQuery);
366 ps.setString(1, username);
367 } catch (ArrayIndexOutOfBoundsException ignore) {
368 // The query may not have any parameters so just try it
370 rs = ps.executeQuery();
371 if (rs.next() == false) {
372 Group group = (Group) groupsMap.get(defaultGroupName);
374 group = createGroup(groupClassName, defaultGroupName);
375 groupsMap.put(defaultGroupName, group);
377 // Check for the tenantManager
378 if(userIsTenantManager(conn, username)) {
379 if (logger.isDebugEnabled()) {
380 logger.debug("GetTenants called with tenantManager - synthesizing the pseudo-tenant");
383 Principal p = createTenant("PseudoTenant", AuthN.TENANT_MANAGER_ACCT_ID);
384 if (logger.isDebugEnabled()) {
385 logger.debug("Assign tenantManager to tenant " + AuthN.TENANT_MANAGER_ACCT_ID);
388 } catch (Exception e) {
389 logger.error("Failed to create pseudo-tenant: " + e.toString());
392 if (logger.isDebugEnabled()) {
393 logger.debug("No tenants found");
395 // We are running with an unauthenticatedIdentity so return an
396 // empty Tenants set.
397 // FIXME should this be allowed?
399 return groupsMap.values();
403 String tenantId = rs.getString(1);
404 String tenantName = rs.getString(2);
405 String groupName = rs.getString(3);
406 if (groupName == null || groupName.length() == 0) {
407 groupName = defaultGroupName;
410 Group group = (Group) groupsMap.get(groupName);
412 group = createGroup(groupClassName, groupName);
413 groupsMap.put(groupName, group);
417 Principal p = createTenant(tenantName, tenantId);
418 if (logger.isDebugEnabled()) {
419 logger.debug("Assign user to tenant " + tenantName);
423 } catch (Exception e) {
424 logger.error("Failed to create tenant: " + tenantName + " " + e.toString());
427 } catch (SQLException ex) {
428 LoginException le = new LoginException("Query failed");
431 } catch (Exception e) {
432 LoginException le = new LoginException("unknown exception");
439 } catch (SQLException e) {
445 } catch (SQLException e) {
451 } catch (Exception ex) {
457 return groupsMap.values();
460 private CSpaceTenant createTenant(String name, String id) throws Exception {
461 return new CSpaceTenant(name, id);
464 private Group createGroup(String groupClassName, String name) throws Exception {
465 return (Group) createPrincipal(groupClassName, name);
468 private Principal createPrincipal(String principalClassName, String name) throws Exception {
469 ClassLoader loader = Thread.currentThread().getContextClassLoader();
470 Class clazz = loader.loadClass(principalClassName);
471 Class[] ctorSig = {String.class};
472 Constructor ctor = clazz.getConstructor(ctorSig);
473 Object[] ctorArgs = {name};
474 Principal p = (Principal) ctor.newInstance(ctorArgs);
478 private Connection getConnection() throws LoginException, SQLException {
479 InitialContext ctx = null;
480 Connection conn = null;
481 String dataSourceName = getDataSourceName();
482 DataSource ds = null;
484 ctx = new InitialContext();
486 ds = (DataSource) ctx.lookup(dataSourceName);
487 } catch (Exception e) {}
490 Context envCtx = (Context) ctx.lookup("java:comp/env");
491 ds = (DataSource) envCtx.lookup(dataSourceName);
492 } catch (Exception e) {}
495 Context envCtx = (Context) ctx.lookup("java:comp");
496 ds = (DataSource) envCtx.lookup(dataSourceName);
497 } catch (Exception e) {}
500 Context envCtx = (Context) ctx.lookup("java:");
501 ds = (DataSource) envCtx.lookup(dataSourceName);
502 } catch (Exception e) {}
505 Context envCtx = (Context) ctx.lookup("java");
506 ds = (DataSource) envCtx.lookup(dataSourceName);
507 } catch (Exception e) {}
510 ds = (DataSource) ctx.lookup("java:/" + dataSourceName);
511 } catch (Exception e) {}
514 ds = AuthN.getDataSource();
518 throw new IllegalArgumentException("datasource not found: " + dataSourceName);
521 conn = ds.getConnection();
523 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.
526 } catch (NamingException ex) {
527 LoginException le = new LoginException("Error looking up DataSource from: " + dataSourceName);
534 } catch (Exception e) {
543 * @return the datasourceName
545 public String getDataSourceName() {
546 return datasourceName;
550 * @return the principalQuery
552 public String getPrincipalQuery() {
553 return principalsQuery;
557 * @param principalQuery the principalQuery to set
559 public void setPrincipalQuery(String principalQuery) {
560 this.principalsQuery = principalQuery;
564 * @return the roleQuery
566 public String getRoleQuery() {
571 * @param roleQuery the roleQuery to set
573 public void setRoleQuery(String roleQuery) {
574 this.rolesQuery = roleQuery;
578 * @return the tenantQuery
580 public String getTenantQuery(boolean includeDisabledTenants) {
581 return includeDisabledTenants?tenantsQueryWithDisabled:tenantsQueryNoDisabled;
585 * @param tenantQuery the tenantQuery to set
586 public void setTenantQuery(String tenantQuery) {
587 this.tenantsQueryNoDisabled = tenantQuery;