Java tutorial
/* * Extensively copied from Spring Security 3.0.5 RoleHierarchyImpl Copyright (C) 2010 University of * Washington. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.opendatakit.security.spring; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.opendatakit.context.CallingContext; import org.opendatakit.persistence.Datastore; import org.opendatakit.persistence.PersistConsts; import org.opendatakit.persistence.TaskLock; import org.opendatakit.persistence.TaskLockType; import org.opendatakit.persistence.exception.ODKDatastoreException; import org.opendatakit.persistence.exception.ODKTaskLockException; import org.opendatakit.persistence.table.GrantedAuthorityHierarchyTable; import org.opendatakit.persistence.table.SecurityRevisionsTable; import org.opendatakit.security.User; import org.opendatakit.security.UserService; import org.opendatakit.security.server.SecurityServiceUtil; import org.opendatakit.utils.WebStartup; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.access.hierarchicalroles.CycleInRoleHierarchyException; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; /** * Much of this implementation is copied verbatim from Spring 3.0.5 RoleHierarchyImpl. The only * difference is the use of InitializingBean and the implementation of the * buildRolesReachableInOneStepMap() which now queries the database for the entries to insert into * the map. * * @author mitchellsundt@gmail.com * @editor cadenh@benetech.org */ public class RoleHierarchyImpl implements RoleHierarchy, InitializingBean { private static final Log logger = LogFactory.getLog(RoleHierarchyImpl.class); // look for flagged changes every CHECK_INTERVAL. private static final long CHECK_INTERVAL = 1000L; // 1 seconds // refresh everything every UPDATE_INTERVAL. private static final long UPDATE_INTERVAL = 2 * 60 * 1000L; // 2 minutes /** bean to the datastore */ private Datastore datastore = null; /** bean to the userService */ private UserService userService = null; /** bean to the startup action */ private WebStartup startupAction = null; private long lastCheckTimestamp = System.currentTimeMillis(); private long lastUpdateTimestamp = System.currentTimeMillis(); /** * rolesReachableInOneOrMoreStepsMap is a Map that under the key of a specific role name contains * a set of all roles reachable from this role in 1 or more steps. * * NOTE: should only be set/accessed with updateRolesMap()/getRolesMap() */ private Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneOrMoreStepsMap = null; public Datastore getDatastore() { return datastore; } public void setDatastore(Datastore datastore) { this.datastore = datastore; } public UserService getUserService() { return userService; } public void setUserService(UserService userService) { this.userService = userService; } public WebStartup getStartupAction() { return startupAction; } public void setStartupAction(WebStartup startupAction) { this.startupAction = startupAction; } @Override public void afterPropertiesSet() throws Exception { logger.info("RoleHierarchyImpl.afterPropertiesSet"); if (datastore == null) { throw new IllegalStateException("datastore cannot be unspecified"); } if (userService == null) { throw new IllegalStateException("userService cannot be unspecified"); } refreshReachableGrantedAuthorities(); CallingContext bootstrapCc = new CallingContext() { @Override public Datastore getDatastore() { return datastore; } @Override public UserService getUserService() { return userService; } @Override public RoleHierarchy getHierarchicalRoleRelationships() { return RoleHierarchyImpl.this; } @Override public void setAsDaemon(boolean asDaemon) { if (asDaemon != true) { throw new IllegalStateException("Invalid context"); } } @Override public boolean getAsDaemon() { return true; } @Override public User getCurrentUser() { return userService.getDaemonAccountUser(); } @Override public String getWebApplicationURL() { throw new IllegalStateException("Undefined"); } @Override public String getWebApplicationURL(String servletAddr) { throw new IllegalStateException("Undefined"); } }; Datastore ds = bootstrapCc.getDatastore(); User user = bootstrapCc.getCurrentUser(); // gain single-access lock record in database... String lockedResourceName = "---startup-serialization-lock---"; String startupLockId = UUID.randomUUID().toString(); int i = 0; boolean locked = false; while (!locked) { if ((++i) % 10 == 0) { logger.warn("excessive wait count for startup serialization lock. Count: " + i); try { Thread.sleep(PersistConsts.MIN_SETTLE_MILLISECONDS); } catch (InterruptedException e) { // we remain in the loop even if we get kicked out. } } else if (i != 1) { try { Thread.sleep(PersistConsts.MIN_SETTLE_MILLISECONDS); } catch (InterruptedException e) { // we remain in the loop even if we get kicked out. } } try { TaskLock startupTaskLock = ds.createTaskLock(user); if (startupTaskLock.obtainLock(startupLockId, lockedResourceName, TaskLockType.STARTUP_SERIALIZATION)) { locked = true; } startupTaskLock = null; } catch (ODKTaskLockException e) { e.printStackTrace(); } } // we hold the lock while we initialize stuff here... try { if (!userService.isAccessManagementConfigured()) { logger.warn("Configuring with default role name and role hierarchy"); SecurityServiceUtil.setDefaultRoleNamesAndHierarchy(bootstrapCc); } // ensure that the superuser has admin privileges SecurityServiceUtil.superUserBootstrap(bootstrapCc); refreshReachableGrantedAuthorities(); if (startupAction != null) { startupAction.doStartupAction(bootstrapCc); } } finally { // release the startup serialization lock try { for (i = 0; i < 10; i++) { TaskLock startupTaskLock = ds.createTaskLock(user); if (startupTaskLock.releaseLock(startupLockId, lockedResourceName, TaskLockType.STARTUP_SERIALIZATION)) { break; } startupTaskLock = null; try { Thread.sleep(PersistConsts.MIN_SETTLE_MILLISECONDS); } catch (InterruptedException e) { // just move on, this retry mechanism // is to make things nice } } } catch (ODKTaskLockException e) { e.printStackTrace(); } } } /** * Update the rolesReachableInOneOrMoreStepsMap with a clean fetch from the database. * * @throws ODKDatastoreException */ public void refreshReachableGrantedAuthorities() throws ODKDatastoreException { logger.info("Executing: refreshReachableGrantedAuthorities"); try { Map<GrantedAuthority, Set<GrantedAuthority>> localRolesReachableInOneOrMoreStepsMap = buildRolesReachableInOneOrMoreStepsMap( buildRolesReachableInOneStepMap()); updateRolesMap(localRolesReachableInOneOrMoreStepsMap); // and wipe the user service, since permissions may have changed... userService.reloadPermissions(); lastCheckTimestamp = lastUpdateTimestamp = System.currentTimeMillis(); } catch (ODKDatastoreException e) { logger.warn("Datastore failure: refreshReachableGrantedAuthorities -- adjusting retry time"); // set ourselves up to hit the database again after half a check interval lastCheckTimestamp = System.currentTimeMillis() - CHECK_INTERVAL / 2L; throw e; } } /** * Atomically swap out the rolesReachableInOneOrMoreStepsMap. * * @param localRolesReachableInOneOrMoreStepsMap */ private synchronized void updateRolesMap( Map<GrantedAuthority, Set<GrantedAuthority>> localRolesReachableInOneOrMoreStepsMap) { rolesReachableInOneOrMoreStepsMap = localRolesReachableInOneOrMoreStepsMap; } /** * Atomically fetch the rolesReachableInOneOrMoreStepsMap. * * @return */ private synchronized Map<GrantedAuthority, Set<GrantedAuthority>> getRolesMap() { return rolesReachableInOneOrMoreStepsMap; } @Override public Collection<? extends GrantedAuthority> getReachableGrantedAuthorities( Collection<? extends GrantedAuthority> authorities) { if (authorities == null || authorities.isEmpty()) { return AuthorityUtils.NO_AUTHORITIES; } long timeRequestStarts = System.currentTimeMillis(); if (timeRequestStarts > lastUpdateTimestamp + UPDATE_INTERVAL) { // update the security configuration entirely every UPDATE_INTERVAL... try { refreshReachableGrantedAuthorities(); } catch (ODKDatastoreException e) { e.printStackTrace(); } } else if (timeRequestStarts > lastCheckTimestamp + CHECK_INTERVAL) { // check for updates to the security configuration every CHECK_INTERVAL... try { User daemon = userService.getDaemonAccountUser(); long lastUsersChange = SecurityRevisionsTable.getLastRegisteredUsersRevisionDate(datastore, daemon); long lastGrantsChange = SecurityRevisionsTable.getLastRoleHierarchyRevisionDate(datastore, daemon); if (lastGrantsChange > lastCheckTimestamp) { refreshReachableGrantedAuthorities(); // NOTE: Timestamps updated and user permissions have been reloaded. } else if (lastUsersChange > lastCheckTimestamp) { lastCheckTimestamp = System.currentTimeMillis(); userService.reloadPermissions(); } else { lastCheckTimestamp = System.currentTimeMillis(); logger.debug("getReachableGrantedAuthorities -- interval check"); } } catch (ODKDatastoreException e) { // log it, but assume we are OK... e.printStackTrace(); } } Map<GrantedAuthority, Set<GrantedAuthority>> localRolesReachableInOneOrMoreStepsMap = getRolesMap(); Set<GrantedAuthority> reachableRoles = new HashSet<GrantedAuthority>(); for (GrantedAuthority authority : authorities) { addReachableRoles(reachableRoles, authority); Set<GrantedAuthority> additionalReachableRoles = getRolesReachableInOneOrMoreSteps( localRolesReachableInOneOrMoreStepsMap, authority); if (additionalReachableRoles != null) { reachableRoles.addAll(additionalReachableRoles); } } if (logger.isDebugEnabled()) { logger.debug("getReachableGrantedAuthorities() - From the roles " + authorities + " one can reach " + reachableRoles + " in zero or more steps."); } List<GrantedAuthority> reachableRoleList = new ArrayList<GrantedAuthority>(reachableRoles.size()); reachableRoleList.addAll(reachableRoles); return reachableRoleList; } // SEC-863 private void addReachableRoles(Set<GrantedAuthority> reachableRoles, GrantedAuthority authority) { Iterator<GrantedAuthority> iterator = reachableRoles.iterator(); while (iterator.hasNext()) { GrantedAuthority testAuthority = iterator.next(); String testKey = testAuthority.getAuthority(); if ((testKey != null) && (testKey.equals(authority.getAuthority()))) { return; } } reachableRoles.add(authority); } // SEC-863 private Set<GrantedAuthority> getRolesReachableInOneOrMoreSteps( Map<GrantedAuthority, Set<GrantedAuthority>> localRolesReachableInOneOrMoreStepsMap, GrantedAuthority authority) { if (authority.getAuthority() == null) { return null; } Iterator<GrantedAuthority> iterator = localRolesReachableInOneOrMoreStepsMap.keySet().iterator(); while (iterator.hasNext()) { GrantedAuthority testAuthority = iterator.next(); String testKey = testAuthority.getAuthority(); if ((testKey != null) && (testKey.equals(authority.getAuthority()))) { return localRolesReachableInOneOrMoreStepsMap.get(testAuthority); } } return null; } /** * For every higher role from rolesReachableInOneStepMap store all roles that are reachable from * it in the map of roles reachable in one or more steps. (Or throw a * CycleInRoleHierarchyException if a cycle in the role hierarchy definition is detected) */ private Map<GrantedAuthority, Set<GrantedAuthority>> buildRolesReachableInOneOrMoreStepsMap( Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap) { Map<GrantedAuthority, Set<GrantedAuthority>> localCopyRolesReachableInOneOrMoreStepsMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>(); // iterate over all higher roles from rolesReachableInOneStepMap for (GrantedAuthority role : rolesReachableInOneStepMap.keySet()) { Set<GrantedAuthority> rolesToVisitSet = new HashSet<GrantedAuthority>(); if (rolesReachableInOneStepMap.containsKey(role)) { rolesToVisitSet.addAll(rolesReachableInOneStepMap.get(role)); } Set<GrantedAuthority> visitedRolesSet = new HashSet<GrantedAuthority>(); while (!rolesToVisitSet.isEmpty()) { // take a role from the rolesToVisit set GrantedAuthority aRole = (GrantedAuthority) rolesToVisitSet.iterator().next(); rolesToVisitSet.remove(aRole); addReachableRoles(visitedRolesSet, aRole); if (rolesReachableInOneStepMap.containsKey(aRole)) { Set<GrantedAuthority> newReachableRoles = rolesReachableInOneStepMap.get(aRole); // definition of a cycle: you can reach the role you are starting from if (rolesToVisitSet.contains(role) || visitedRolesSet.contains(role)) { throw new CycleInRoleHierarchyException(); } else { // no cycle rolesToVisitSet.addAll(newReachableRoles); } } } localCopyRolesReachableInOneOrMoreStepsMap.put(role, visitedRolesSet); logger.debug("buildRolesReachableInOneOrMoreStepsMap() - From role " + role + " one can reach " + visitedRolesSet + " in one or more steps."); } return localCopyRolesReachableInOneOrMoreStepsMap; } /////////////////////////////////////////////////////////////////////////// // Code added for ODK Aggregate implementation /** * Parse input and build the map for the roles reachable in one step: the higher role will become * a key that references a set of the reachable lower roles. * * @throws ODKDatastoreException */ private synchronized Map<GrantedAuthority, Set<GrantedAuthority>> buildRolesReachableInOneStepMap() throws ODKDatastoreException { Map<GrantedAuthority, Set<GrantedAuthority>> rolesReachableInOneStepMap = new HashMap<GrantedAuthority, Set<GrantedAuthority>>(); User user = userService.getDaemonAccountUser(); TreeMap<String, TreeSet<String>> oneStepRelations = GrantedAuthorityHierarchyTable .getEntireGrantedAuthorityHierarchy(datastore, user); for (Map.Entry<String, TreeSet<String>> e : oneStepRelations.entrySet()) { GrantedAuthority higherRole = new SimpleGrantedAuthority(e.getKey()); Set<GrantedAuthority> rolesReachableInOneStepSet = null; if (!rolesReachableInOneStepMap.containsKey(higherRole)) { rolesReachableInOneStepSet = new HashSet<GrantedAuthority>(); rolesReachableInOneStepMap.put(higherRole, rolesReachableInOneStepSet); } else { rolesReachableInOneStepSet = rolesReachableInOneStepMap.get(higherRole); } for (String s : e.getValue()) { GrantedAuthority lowerRole = new SimpleGrantedAuthority(s); addReachableRoles(rolesReachableInOneStepSet, lowerRole); logger.debug("buildRolesReachableInOneStepMap() - From role " + higherRole + " one can reach role " + lowerRole + " in one step."); } } return rolesReachableInOneStepMap; } }