Java tutorial
/* * Copyright (C) 2011 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.server; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.opendatakit.context.CallingContext; import org.opendatakit.persistence.CommonFieldsBase; import org.opendatakit.persistence.Datastore; import org.opendatakit.persistence.Query; import org.opendatakit.persistence.client.exception.DatastoreFailureException; import org.opendatakit.persistence.exception.ODKDatastoreException; import org.opendatakit.persistence.table.GrantedAuthorityHierarchyTable; import org.opendatakit.persistence.table.RegisteredUsersTable; import org.opendatakit.persistence.table.SecurityRevisionsTable; import org.opendatakit.persistence.table.UserGrantedAuthority; import org.opendatakit.security.User; import org.opendatakit.security.client.CredentialsInfo; import org.opendatakit.security.client.UserSecurityInfo; import org.opendatakit.security.client.UserSecurityInfo.UserType; import org.opendatakit.security.client.exception.AccessDeniedException; import org.opendatakit.security.common.GrantedAuthorityName; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; /** * Common utility methods extracted from the AccessConfigurationServlet so they can be shared * between the servlet and GWT server classes. * * @author mitchellsundt@gmail.com * */ public class SecurityServiceUtil { private static final Set<String> specialNames = new HashSet<String>(); public static final GrantedAuthority siteAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_SITE_ADMINS.name()); public static final List<String> siteAdministratorGrants; public static final GrantedAuthority dataOwnerAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_FORM_MANAGERS.name()); public static final List<String> dataOwnerGrants; public static final GrantedAuthority administerTablesAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_ADMINISTER_TABLES.name()); public static final List<String> administerTablesGrants; public static final GrantedAuthority superUserTablesAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_SUPER_USER_TABLES.name()); public static final List<String> superUserTablesGrants; public static final GrantedAuthority synchronizeTablesAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_SYNCHRONIZE_TABLES.name()); public static final List<String> synchronizeTablesGrants; public static final GrantedAuthority dataViewerAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_DATA_VIEWERS.name()); public static final List<String> dataViewerGrants; public static final GrantedAuthority dataCollectorAuth = new SimpleGrantedAuthority( GrantedAuthorityName.GROUP_DATA_COLLECTORS.name()); public static final List<String> dataCollectorGrants; public static final GrantedAuthority anonAuth = new SimpleGrantedAuthority( GrantedAuthorityName.USER_IS_ANONYMOUS.name()); // special grants for Google Earth work-around public static final List<String> anonAttachmentViewerGrants; static { List<String> isiteAdministratorGrants = new ArrayList<String>(); isiteAdministratorGrants.add(GrantedAuthorityName.ROLE_USER.name()); isiteAdministratorGrants.add(GrantedAuthorityName.ROLE_SITE_ACCESS_ADMIN.name()); isiteAdministratorGrants.add(GrantedAuthorityName.GROUP_ADMINISTER_TABLES.name()); isiteAdministratorGrants.add(GrantedAuthorityName.GROUP_SYNCHRONIZE_TABLES.name()); isiteAdministratorGrants.add(GrantedAuthorityName.GROUP_FORM_MANAGERS.name()); isiteAdministratorGrants.add(GrantedAuthorityName.GROUP_DATA_VIEWERS.name()); siteAdministratorGrants = Collections.unmodifiableList(isiteAdministratorGrants); List<String> iadministerTablesGrants = new ArrayList<String>(); iadministerTablesGrants.add(GrantedAuthorityName.ROLE_USER.name()); iadministerTablesGrants.add(GrantedAuthorityName.ROLE_ADMINISTER_TABLES.name()); iadministerTablesGrants.add(GrantedAuthorityName.GROUP_SUPER_USER_TABLES.name()); administerTablesGrants = Collections.unmodifiableList(iadministerTablesGrants); List<String> isuperUserTablesGrants = new ArrayList<String>(); isuperUserTablesGrants.add(GrantedAuthorityName.ROLE_USER.name()); isuperUserTablesGrants.add(GrantedAuthorityName.ROLE_SUPER_USER_TABLES.name()); isuperUserTablesGrants.add(GrantedAuthorityName.GROUP_SYNCHRONIZE_TABLES.name()); superUserTablesGrants = Collections.unmodifiableList(isuperUserTablesGrants); List<String> isynchronizeTablesGrants = new ArrayList<String>(); isynchronizeTablesGrants.add(GrantedAuthorityName.ROLE_USER.name()); isynchronizeTablesGrants.add(GrantedAuthorityName.ROLE_SYNCHRONIZE_TABLES.name()); synchronizeTablesGrants = Collections.unmodifiableList(isynchronizeTablesGrants); List<String> idataOwnerGrants = new ArrayList<String>(); idataOwnerGrants.add(GrantedAuthorityName.ROLE_USER.name()); idataOwnerGrants.add(GrantedAuthorityName.ROLE_DATA_OWNER.name()); idataOwnerGrants.add(GrantedAuthorityName.GROUP_DATA_VIEWERS.name()); dataOwnerGrants = Collections.unmodifiableList(idataOwnerGrants); List<String> idataViewerGrants = new ArrayList<String>(); idataViewerGrants.add(GrantedAuthorityName.ROLE_USER.name()); idataViewerGrants.add(GrantedAuthorityName.ROLE_DATA_VIEWER.name()); dataViewerGrants = Collections.unmodifiableList(idataViewerGrants); List<String> idataCollectorGrants = new ArrayList<String>(); idataCollectorGrants.add(GrantedAuthorityName.ROLE_DATA_COLLECTOR.name()); dataCollectorGrants = Collections.unmodifiableList(idataCollectorGrants); // Work-around hack for Google Earth top-level balloon image List<String> ianonAttachmentViewerGrants = new ArrayList<String>(); anonAttachmentViewerGrants = Collections.unmodifiableList(ianonAttachmentViewerGrants); } /** * Return all registered users and the Anonymous user. * * @param withAuthorities * @param cc * @return * @throws AccessDeniedException * @throws DatastoreFailureException */ public static ArrayList<UserSecurityInfo> getAllUsers(boolean withAuthorities, CallingContext cc) throws AccessDeniedException, DatastoreFailureException { ArrayList<UserSecurityInfo> users = new ArrayList<UserSecurityInfo>(); try { Query q = RegisteredUsersTable.createQuery(cc.getDatastore(), "SecurityServiceUtil.getAllUsers", cc.getCurrentUser()); RegisteredUsersTable.applyNaturalOrdering(q, cc); List<? extends CommonFieldsBase> l = q.executeQuery(); for (CommonFieldsBase cb : l) { RegisteredUsersTable t = (RegisteredUsersTable) cb; UserSecurityInfo i = new UserSecurityInfo(t.getUsername(), t.getFullName(), t.getEmail(), UserSecurityInfo.UserType.REGISTERED, t.getOfficeId()); if (withAuthorities) { SecurityServiceUtil.setAuthenticationLists(i, t.getUri(), cc); } users.add(i); } // TODO: why doesn't this work? UserSecurityInfo anonymous = new UserSecurityInfo(User.ANONYMOUS_USER, User.ANONYMOUS_USER_NICKNAME, null, UserSecurityInfo.UserType.ANONYMOUS); if (withAuthorities) { SecurityServiceUtil.setAuthenticationListsForSpecialUser(anonymous, GrantedAuthorityName.USER_IS_ANONYMOUS, cc); } users.add(anonymous); } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException(e); } // the natural ordering (above) produces a sorted list... return users; } public static HashMap<String, UserSecurityInfo> getUriUserSecurityInfoMap(boolean withAuthorities, CallingContext cc) throws AccessDeniedException, DatastoreFailureException { HashMap<String, UserSecurityInfo> users = new HashMap<String, UserSecurityInfo>(); try { Query q = RegisteredUsersTable.createQuery(cc.getDatastore(), "SecurityServiceUtil.getAllUsers", cc.getCurrentUser()); RegisteredUsersTable.applyNaturalOrdering(q, cc); List<? extends CommonFieldsBase> l = q.executeQuery(); for (CommonFieldsBase cb : l) { RegisteredUsersTable t = (RegisteredUsersTable) cb; UserSecurityInfo i = new UserSecurityInfo(t.getUsername(), t.getFullName(), t.getEmail(), UserSecurityInfo.UserType.REGISTERED, t.getOfficeId()); if (withAuthorities) { SecurityServiceUtil.setAuthenticationLists(i, t.getUri(), cc); } users.put(t.getUri(), i); } // TODO: why doesn't this work? UserSecurityInfo anonymous = new UserSecurityInfo(User.ANONYMOUS_USER, User.ANONYMOUS_USER_NICKNAME, null, UserSecurityInfo.UserType.ANONYMOUS); if (withAuthorities) { SecurityServiceUtil.setAuthenticationListsForSpecialUser(anonymous, GrantedAuthorityName.USER_IS_ANONYMOUS, cc); } users.put(User.ANONYMOUS_USER, anonymous); } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException(e); } return users; } static GrantedAuthorityName mapName(GrantedAuthority auth, Set<GrantedAuthority> badGrants) { GrantedAuthorityName name = null; try { name = GrantedAuthorityName.valueOf(auth.getAuthority()); } catch (Exception e) { badGrants.add(auth); } return name; } /** * During upgrades or other operations, we may change the set of granted authorities (the valid * set are identified by GrantedAuthorityNames). Remove the bad grants wherever we find them... * * @param badGrants * @throws ODKDatastoreException */ static void removeBadGrantedAuthorities(Set<GrantedAuthority> badGrants, CallingContext cc) throws ODKDatastoreException { ArrayList<String> empty = new ArrayList<String>(); for (GrantedAuthority auth : badGrants) { UserGrantedAuthority.assertGrantedAuthorityMembers(auth, empty, cc); } } public static void setAuthenticationLists(UserSecurityInfo userInfo, String uriUser, CallingContext cc) throws ODKDatastoreException { Datastore ds = cc.getDatastore(); User user = cc.getCurrentUser(); RoleHierarchy hierarchy = (RoleHierarchy) cc.getHierarchicalRoleRelationships(); Set<GrantedAuthority> grants = UserGrantedAuthority.getGrantedAuthorities(uriUser, ds, user); Set<GrantedAuthority> badGrants = new TreeSet<GrantedAuthority>(); TreeSet<GrantedAuthorityName> groups = new TreeSet<GrantedAuthorityName>(); TreeSet<GrantedAuthorityName> authorities = new TreeSet<GrantedAuthorityName>(); for (GrantedAuthority grant : grants) { GrantedAuthorityName name = mapName(grant, badGrants); if (name != null) { if (GrantedAuthorityName.permissionsCanBeAssigned(grant.getAuthority())) { groups.add(name); } else { authorities.add(name); } } } Collection<? extends GrantedAuthority> auths = hierarchy.getReachableGrantedAuthorities(grants); for (GrantedAuthority auth : auths) { GrantedAuthorityName name = mapName(auth, badGrants); if (name != null && !GrantedAuthorityName.permissionsCanBeAssigned(auth.getAuthority())) { authorities.add(name); } } userInfo.setAssignedUserGroups(groups); userInfo.setGrantedAuthorities(authorities); removeBadGrantedAuthorities(badGrants, cc); } public static void setAuthenticationListsForSpecialUser(UserSecurityInfo userInfo, GrantedAuthorityName specialGroup, CallingContext cc) throws DatastoreFailureException { RoleHierarchy hierarchy = (RoleHierarchy) cc.getHierarchicalRoleRelationships(); Set<GrantedAuthority> badGrants = new TreeSet<GrantedAuthority>(); // The assigned groups are the specialGroup that this user defines // (i.e., anonymous or daemon) plus all directly-assigned assignable // permissions. TreeSet<GrantedAuthorityName> groups = new TreeSet<GrantedAuthorityName>(); TreeSet<GrantedAuthorityName> authorities = new TreeSet<GrantedAuthorityName>(); groups.add(specialGroup); GrantedAuthority specialAuth = new SimpleGrantedAuthority(specialGroup.name()); try { Set<GrantedAuthority> auths = GrantedAuthorityHierarchyTable .getSubordinateGrantedAuthorities(specialAuth, cc); for (GrantedAuthority auth : auths) { GrantedAuthorityName name = mapName(auth, badGrants); if (name != null) { groups.add(name); } } } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Unable to retrieve granted authorities of " + specialGroup.name()); } Collection<? extends GrantedAuthority> auths = hierarchy .getReachableGrantedAuthorities(Collections.singletonList(specialAuth)); for (GrantedAuthority auth : auths) { GrantedAuthorityName name = mapName(auth, badGrants); if (name != null && !GrantedAuthorityName.permissionsCanBeAssigned(auth.getAuthority())) { authorities.add(name); } } userInfo.setAssignedUserGroups(groups); userInfo.setGrantedAuthorities(authorities); try { removeBadGrantedAuthorities(badGrants, cc); } catch (ODKDatastoreException e) { e.printStackTrace(); } } /** * Get the complete set of granted authorities (ROLE and RUN_AS grants) this user possesses. * * @param cc * @return * @throws ODKDatastoreException */ public static TreeSet<GrantedAuthorityName> getCurrentUserSecurityInfo(CallingContext cc) throws ODKDatastoreException { User user = cc.getCurrentUser(); TreeSet<GrantedAuthorityName> authorities = new TreeSet<GrantedAuthorityName>(); if (user.isAnonymous()) { RoleHierarchy hierarchy = cc.getHierarchicalRoleRelationships(); Set<GrantedAuthority> badGrants = new TreeSet<GrantedAuthority>(); // The assigned groups are the specialGroup that this user defines // (i.e., anonymous or daemon) plus all directly-assigned assignable // permissions. GrantedAuthority specialAuth = new SimpleGrantedAuthority( GrantedAuthorityName.USER_IS_ANONYMOUS.name()); Collection<? extends GrantedAuthority> auths = hierarchy .getReachableGrantedAuthorities(Collections.singletonList(specialAuth)); for (GrantedAuthority auth : auths) { GrantedAuthorityName name = mapName(auth, badGrants); if (name != null && !GrantedAuthorityName.permissionsCanBeAssigned(auth.getAuthority())) { authorities.add(name); } } removeBadGrantedAuthorities(badGrants, cc); } else { RegisteredUsersTable t; t = RegisteredUsersTable.getUserByUri(user.getUriUser(), cc.getDatastore(), user); Datastore ds = cc.getDatastore(); RoleHierarchy hierarchy = (RoleHierarchy) cc.getHierarchicalRoleRelationships(); Set<GrantedAuthority> grants = UserGrantedAuthority.getGrantedAuthorities(user.getUriUser(), ds, user); Set<GrantedAuthority> badGrants = new TreeSet<GrantedAuthority>(); TreeSet<GrantedAuthorityName> groups = new TreeSet<GrantedAuthorityName>(); for (GrantedAuthority grant : grants) { GrantedAuthorityName name = mapName(grant, badGrants); if (name != null) { if (GrantedAuthorityName.permissionsCanBeAssigned(grant.getAuthority())) { groups.add(name); } else { authorities.add(name); } } } Collection<? extends GrantedAuthority> auths = hierarchy.getReachableGrantedAuthorities(grants); for (GrantedAuthority auth : auths) { GrantedAuthorityName name = mapName(auth, badGrants); if (name != null && !GrantedAuthorityName.permissionsCanBeAssigned(auth.getAuthority())) { authorities.add(name); } } removeBadGrantedAuthorities(badGrants, cc); } return authorities; } public static final synchronized boolean isSpecialName(String authority) { if (SecurityServiceUtil.specialNames.isEmpty()) { for (GrantedAuthorityName n : GrantedAuthorityName.values()) { SecurityServiceUtil.specialNames.add(n.name()); } } return SecurityServiceUtil.specialNames.contains(authority) || authority.startsWith(GrantedAuthorityName.RUN_AS_PREFIX) || authority.startsWith(GrantedAuthorityName.ROLE_PREFIX); } /** * Given a collection of users, ensure that each user is a registered user (creating a registered * user if one doesn't exist). * </p> * <p> * The collection is assumed to be exhaustive. Users not in the list will be deleted. * </p> * * @param users * @param cc * @return map of users to their Uri strings * @throws DatastoreFailureException * @throws AccessDeniedException */ private static Map<UserSecurityInfo, String> setUsers(ArrayList<UserSecurityInfo> users, CallingContext cc) throws DatastoreFailureException, AccessDeniedException { List<UserSecurityInfo> allUsersList = getAllUsers(false, cc); Set<UserSecurityInfo> removedUsers = new TreeSet<UserSecurityInfo>(); removedUsers.addAll(allUsersList); removedUsers.removeAll(users); Datastore ds = cc.getDatastore(); User user = cc.getCurrentUser(); Map<UserSecurityInfo, String> pkMap = new HashMap<UserSecurityInfo, String>(); try { // mark absent users as removed... for (UserSecurityInfo u : removedUsers) { if (u.getType() != UserType.REGISTERED) continue; RegisteredUsersTable t; if (u.getUsername() == null) { t = RegisteredUsersTable.getUniqueUserByEmail(u.getEmail(), ds, user); } else { t = RegisteredUsersTable.getUniqueUserByUsername(u.getUsername(), ds, user); } if (t != null) { t.setIsRemoved(true); ds.putEntity(t, user); } } // go through all other users. Assert that they exist. // This will update the fields to match those specified. for (UserSecurityInfo u : users) { if (u.getType() != UserType.REGISTERED) continue; RegisteredUsersTable t = RegisteredUsersTable.assertActiveUserByUserSecurityInfo(u, cc); pkMap.put(u, t.getUri()); } } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Incomplete security update", e); } return pkMap; } /** * Given a collection of users, ensure that each user is a registered user (creating a registered * user if one doesn't exist) and assign those users to the granted authority. * <p> * The collection is assumed to be exhaustive. If there are other e-mails already assigned to the * granted authority, they will be removed so that exactly the passed-in set of users are assigned * to the authority, no more, no less. * </p> * * @param users * @param auth * @param cc * @throws DatastoreFailureException */ private static void setUsersOfGrantedAuthority(Map<UserSecurityInfo, String> pkMap, GrantedAuthority auth, CallingContext cc) throws DatastoreFailureException { Set<GrantedAuthority> badGrants = new TreeSet<GrantedAuthority>(); GrantedAuthorityName name = mapName(auth, badGrants); if (name != null) { // build the set of uriUsers for this granted authority... TreeSet<String> desiredMembers = new TreeSet<String>(); for (Map.Entry<UserSecurityInfo, String> u : pkMap.entrySet()) { UserSecurityInfo info = u.getKey(); String uriUser = u.getValue(); if (info.getAssignedUserGroups().contains(name)) { desiredMembers.add(uriUser); } } // assert that the authority has exactly this set of uriUsers (no more, no // less) try { UserGrantedAuthority.assertGrantedAuthorityMembers(auth, desiredMembers, cc); } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Incomplete security update", e); } } else { try { removeBadGrantedAuthorities(badGrants, cc); } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Incomplete security update", e); } } } /** * Method to enforce an access configuration constraining only registered users, authenticated * users and anonymous access. * * Add additional checks of the incoming parameters and patch things up if the incoming list of * users omits the super-user. * * @param users * @param anonGrants * @param allGroups * @param cc * @throws DatastoreFailureException * @throws AccessDeniedException */ public static final void setStandardSiteAccessConfiguration(ArrayList<UserSecurityInfo> users, ArrayList<GrantedAuthorityName> allGroups, CallingContext cc) throws DatastoreFailureException, AccessDeniedException { // remove anonymousUser from the set of users and collect its // permissions (anonGrantStrings) which will be placed in // the granted authority hierarchy table. List<String> anonGrantStrings = new ArrayList<String>(); { UserSecurityInfo anonUser = null; for (UserSecurityInfo i : users) { if (i.getType() == UserType.ANONYMOUS) { anonUser = i; // clean up grants for anonymousUser -- // ignore anonAuth (the grant under which we will place things) // and forbid Site Admin for (GrantedAuthorityName a : i.getAssignedUserGroups()) { if (anonAuth.getAuthority().equals(a.name())) continue; // avoid circularity... // only allow ROLE_ATTACHMENT_VIEWER and GROUP_ assignments. if (!a.name().startsWith(GrantedAuthorityName.GROUP_PREFIX)) { continue; } // do not allow Site Admin assignments for Anonymous -- // or Tables super-user or Tables Administrator. // those all give access to the full set of users on the system // and giving that information to Anonymous is a security // risk. if (GrantedAuthorityName.GROUP_SITE_ADMINS.equals(a) || GrantedAuthorityName.GROUP_ADMINISTER_TABLES.equals(a) || GrantedAuthorityName.GROUP_SUPER_USER_TABLES.equals(a)) { continue; } anonGrantStrings.add(a.name()); } break; } } if (anonUser != null) { users.remove(anonUser); } } // scan through the users and remove any entries under assigned user groups // that do not begin with GROUP_. // // Additionally, if the user is an e-mail, remove the GROUP_DATA_COLLECTORS // permission since ODK Collect does not support oauth2 authentication. { TreeSet<GrantedAuthorityName> toRemove = new TreeSet<GrantedAuthorityName>(); for (UserSecurityInfo i : users) { // only working with registered users if (i.getType() != UserType.REGISTERED) { continue; } // get the list of assigned groups // -- this is not a copy -- we can directly manipulate this. TreeSet<GrantedAuthorityName> assignedGroups = i.getAssignedUserGroups(); // scan the set of assigned groups and remove any that don't begin with GROUP_ toRemove.clear(); for (GrantedAuthorityName name : assignedGroups) { if (!name.name().startsWith(GrantedAuthorityName.GROUP_PREFIX)) { toRemove.add(name); } } if (!toRemove.isEmpty()) { assignedGroups.removeAll(toRemove); } // for e-mail accounts, remove the Data Collector permission since ODK Collect // does not support an oauth2 authentication mechanism. if (i.getEmail() != null) { assignedGroups.remove(GrantedAuthorityName.GROUP_DATA_COLLECTORS); } } } // find the entry(entries) for the designated super-user(s) String superUserUsername = cc.getUserService().getSuperUserUsername(); int expectedSize = ((superUserUsername != null) ? 1 : 0); ArrayList<UserSecurityInfo> superUsers = new ArrayList<UserSecurityInfo>(); for (UserSecurityInfo i : users) { if (i.getType() == UserType.REGISTERED) { if (i.getUsername() != null && superUserUsername != null && i.getUsername().equals(superUserUsername)) { superUsers.add(i); } } } if (superUsers.size() != expectedSize) { // we are missing one or both super-users. // remove any we have and recreate them from scratch. users.removeAll(superUsers); superUsers.clear(); // Synthesize a UserSecurityInfo object for the super-user(s) // and add it(them) to the list. try { List<RegisteredUsersTable> tList = RegisteredUsersTable.assertSuperUsers(cc); for (RegisteredUsersTable t : tList) { UserSecurityInfo i = new UserSecurityInfo(t.getUsername(), t.getFullName(), t.getEmail(), UserSecurityInfo.UserType.REGISTERED); superUsers.add(i); users.add(i); } } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Incomplete update"); } } // reset super-user privileges to have (just) site admin privileges // even if caller attempts to change, add, or remove them. for (UserSecurityInfo i : superUsers) { TreeSet<GrantedAuthorityName> grants = new TreeSet<GrantedAuthorityName>(); grants.add(GrantedAuthorityName.GROUP_SITE_ADMINS); grants.add(GrantedAuthorityName.ROLE_SITE_ACCESS_ADMIN); // override whatever the user gave us. i.setAssignedUserGroups(grants); } try { // enforce our fixed set of groups and their inclusion hierarchy. // this is generally a no-op during normal operations. GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(siteAuth, SecurityServiceUtil.siteAdministratorGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(administerTablesAuth, SecurityServiceUtil.administerTablesGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(superUserTablesAuth, SecurityServiceUtil.superUserTablesGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(synchronizeTablesAuth, SecurityServiceUtil.synchronizeTablesGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(dataOwnerAuth, SecurityServiceUtil.dataOwnerGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(dataViewerAuth, SecurityServiceUtil.dataViewerGrants, cc); GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(dataCollectorAuth, SecurityServiceUtil.dataCollectorGrants, cc); // place the anonymous user's permissions in the granted authority table. GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(anonAuth, anonGrantStrings, cc); // get all granted authority names TreeSet<String> authorities = GrantedAuthorityHierarchyTable .getAllPermissionsAssignableGrantedAuthorities(cc.getDatastore(), cc.getCurrentUser()); // remove the groups that have structure (i.e., those defined above). authorities.remove(siteAuth.getAuthority()); authorities.remove(administerTablesAuth.getAuthority()); authorities.remove(superUserTablesAuth.getAuthority()); authorities.remove(synchronizeTablesAuth.getAuthority()); authorities.remove(dataOwnerAuth.getAuthority()); authorities.remove(dataViewerAuth.getAuthority()); authorities.remove(dataCollectorAuth.getAuthority()); authorities.remove(anonAuth.getAuthority()); // delete all hierarchy structures under anything else. // i.e., if somehow USER_IS_REGISTERED had been granted GROUP_FORM_MANAGER // then this loop would leave USER_IS_REGISTERED without any grants. // (it repairs the database to conform to our privilege hierarchy expectations). List<String> empty = Collections.emptyList(); for (String s : authorities) { GrantedAuthorityHierarchyTable.assertGrantedAuthorityHierarchy(new SimpleGrantedAuthority(s), empty, cc); } // declare all the users (and remove users that are not in this set) Map<UserSecurityInfo, String> pkMap = setUsers(users, cc); // now, for each GROUP_..., update the user granted authority // table with the users that have that GROUP_... assignment. setUsersOfGrantedAuthority(pkMap, siteAuth, cc); setUsersOfGrantedAuthority(pkMap, administerTablesAuth, cc); setUsersOfGrantedAuthority(pkMap, superUserTablesAuth, cc); setUsersOfGrantedAuthority(pkMap, synchronizeTablesAuth, cc); setUsersOfGrantedAuthority(pkMap, dataOwnerAuth, cc); setUsersOfGrantedAuthority(pkMap, dataViewerAuth, cc); setUsersOfGrantedAuthority(pkMap, dataCollectorAuth, cc); // all super-users would already have their site admin role and // we leave that unchanged. The key is to ensure that the // super users are in the users list so they don't get // accidentally removed and that they have siteAuth group // membership. I.e., we don't need to manage ROLE_SITE_ACCESS_ADMIN // here. it is done elsewhere. } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException("Incomplete update"); } finally { Datastore ds = cc.getDatastore(); User user = cc.getCurrentUser(); try { SecurityRevisionsTable.setLastRegisteredUsersRevisionDate(ds, user); } catch (ODKDatastoreException e) { // if it fails, use RELOAD_INTERVAL to force reload. e.printStackTrace(); } try { SecurityRevisionsTable.setLastRoleHierarchyRevisionDate(ds, user); } catch (ODKDatastoreException e) { // if it fails, use RELOAD_INTERVAL to force reload. e.printStackTrace(); } } } public static final void setUserCredentials(CredentialsInfo credential, CallingContext cc) throws AccessDeniedException, DatastoreFailureException { Datastore ds = cc.getDatastore(); User user = cc.getUserService().getCurrentUser(); RegisteredUsersTable userDefinition = null; try { userDefinition = RegisteredUsersTable.getUserByUsername(credential.getUsername(), cc.getUserService(), ds); if (userDefinition == null) { throw new AccessDeniedException("User is not a registered user."); } userDefinition.setDigestAuthPassword(credential.getDigestAuthHash()); userDefinition.setBasicAuthPassword(credential.getBasicAuthHash()); userDefinition.setBasicAuthSalt(credential.getBasicAuthSalt()); ds.putEntity(userDefinition, user); } catch (ODKDatastoreException e) { e.printStackTrace(); throw new DatastoreFailureException(e.getMessage()); } } /** * Configures the server to have the default role names and role hierarchy. * * @param cc * @throws DatastoreFailureException * @throws AccessDeniedException */ public static final void setDefaultRoleNamesAndHierarchy(CallingContext cc) throws DatastoreFailureException, AccessDeniedException { ArrayList<UserSecurityInfo> users = new ArrayList<UserSecurityInfo>(); ArrayList<GrantedAuthorityName> allGroups = new ArrayList<GrantedAuthorityName>(); // Grant the Anonymous user the ability to submit data to ODK Aggregate // Enables users to anonymously publish from ODK Collect into ODK Aggregate UserSecurityInfo anonymous = new UserSecurityInfo(User.ANONYMOUS_USER, User.ANONYMOUS_USER_NICKNAME, null, UserSecurityInfo.UserType.ANONYMOUS); TreeSet<GrantedAuthorityName> userGroups = new TreeSet<GrantedAuthorityName>(); userGroups.add(GrantedAuthorityName.GROUP_DATA_COLLECTORS); userGroups.add(GrantedAuthorityName.GROUP_FORM_MANAGERS); // issue 710 anonymous.setAssignedUserGroups(userGroups); users.add(anonymous); // NOTE: No users are defined at this point (including the superUser) see // superUserBootstrap below... setStandardSiteAccessConfiguration(users, allGroups, cc); } /** * Ensures that a (single) registered user record exists for the superUser, adds that user to the * list of site administrators, establishes that user as the sole user with permanent access to * the permissions management tab, and, if the user is new, it sets a flag to force the user to * visit the permissions tab upon first access to the site (this is done inside assertSuperUser). * * @param cc * @throws ODKDatastoreException */ public static final synchronized void superUserBootstrap(CallingContext cc) throws ODKDatastoreException { // assert that the superuser exists... List<RegisteredUsersTable> suList = RegisteredUsersTable.assertSuperUsers(cc); Set<String> uriUsers; // add the superuser to the list of site administrators uriUsers = UserGrantedAuthority.getUriUsers(siteAuth, cc.getDatastore(), cc.getCurrentUser()); for (RegisteredUsersTable su : suList) { uriUsers.add(su.getUri()); } UserGrantedAuthority.assertGrantedAuthorityMembers(siteAuth, uriUsers, cc); // assert that the superuser is the only one with permanent access // administration rights... uriUsers.clear(); for (RegisteredUsersTable su : suList) { uriUsers.add(su.getUri()); } UserGrantedAuthority.assertGrantedAuthorityMembers( new SimpleGrantedAuthority(GrantedAuthorityName.ROLE_SITE_ACCESS_ADMIN.name()), uriUsers, cc); } }