/**
* Copyright 2008 The University of North Carolina at Chapel Hill
*
* 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 edu.unc.lib.dl.acl.util;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import edu.unc.lib.dl.fedora.PID;
import edu.unc.lib.dl.util.ContentModelHelper;
import edu.unc.lib.dl.util.DateTimeUtil;
import edu.unc.lib.dl.xml.JDOMNamespaceUtil;
/**
* Encapsulates the complete set of access controls that apply to a particular object.
*
* @author count0
*
*/
public class ObjectAccessControlsBean {
private static final Logger LOG = LoggerFactory.getLogger(ObjectAccessControlsBean.class);
private static final String ACTIVE_STATE = ContentModelHelper.FedoraProperty.Active.toString();
PID object = null;
// Inherited or directly applied groups
Map<UserRole, Set<String>> baseRoleGroups = null;
// Global roles
Map<UserRole, Set<String>> globalRoleGroups = null;
// Roles after merging base, global and embargoes
Map<UserRole, Set<String>> activeRoleGroups = null;
List<Date> activeEmbargoes = null;
boolean ancestorsPublished = true;
Boolean isPublished = true;
Boolean isActive = true;
boolean ancestorsActive = true;
public ObjectAccessControlsBean(PID pid, Map<UserRole, Set<String>> baseRoleGroups) {
this.object = pid;
this.baseRoleGroups = baseRoleGroups;
this.activeRoleGroups = this.getMergedRoleGroups();
}
/**
* Constructs a new ObjectAccessControlsBean object from a collection of pipe delimited role uri/group pairings,
* representing all role/group relationships assigned to this object
*
* @param pid
* @param roleGroups
*/
public ObjectAccessControlsBean(PID pid, Collection<String> roleGroups) {
this.object = pid;
this.baseRoleGroups = new HashMap<UserRole, Set<String>>();
for (String roleGroup : roleGroups) {
LOG.debug("roleGroup: " + roleGroup);
String[] roleGroupArray = roleGroup.split("\\|");
if (roleGroupArray.length == 2) {
String role = roleGroupArray[0];
if (role.indexOf('#') == -1) {
role = JDOMNamespaceUtil.CDR_ROLE_NS.getURI() + role;
}
UserRole userRole = UserRole.getUserRole(role);
if (userRole == null) {
continue;
}
Set<String> groupSet = baseRoleGroups.get(userRole);
if (groupSet == null) {
groupSet = new HashSet<String>();
baseRoleGroups.put(userRole, groupSet);
}
groupSet.add(roleGroupArray[1]);
}
}
this.activeRoleGroups = this.getMergedRoleGroups();
}
/**
* Constructs a new ObjectAccessControlsBean object from a map of role/group relations and active embargoes
*
* @param pid
* @param roles
* @param embargoes
*/
public ObjectAccessControlsBean(PID pid, Map<String, ? extends Collection<String>> roles,
Map<String, ? extends Collection<String>> globalRoles, Collection<String> embargoes,
Collection<String> publicationStatus, Collection<String> objectState) {
this.object = pid;
this.baseRoleGroups = new HashMap<UserRole, Set<String>>();
this.globalRoleGroups = new HashMap<UserRole, Set<String>>();
if (objectState != null) {
this.isActive = !objectState.contains("Deleted");
this.ancestorsActive = !objectState.contains("Deleted Ancestor");
}
if (publicationStatus != null) {
this.isPublished = !publicationStatus.contains("Unpublished");
this.ancestorsPublished = !publicationStatus.contains("Unpublished Ancestor");
}
copyRoles(roles, this.baseRoleGroups);
copyRoles(globalRoles, this.globalRoleGroups);
extractEmbargoes(embargoes);
this.activeRoleGroups = this.getMergedRoleGroups();
}
/**
* Construct a new access control bean by applying triples from an item on top of an existing access control bean as
* if it were the parent for the new object
*
* @param baseAcls
* parent objects access control information
* @param pid
* pid of the new object
* @param triples
* map of triples containing the access control of the new object
*/
public ObjectAccessControlsBean(ObjectAccessControlsBean baseAcls, PID pid, Map<String, List<String>> triples) {
this.object = pid;
List<String> inherit = triples.get(ContentModelHelper.CDRProperty.inheritPermissions.toString());
boolean inheritPermissions = inherit == null || inherit.size() == 0 || !"false".equals(inherit.get(0));
if (inheritPermissions) {
this.baseRoleGroups = new HashMap<UserRole, Set<String>>(baseAcls.baseRoleGroups);
if (baseAcls.activeEmbargoes != null)
this.activeEmbargoes = new ArrayList<Date>(baseAcls.activeEmbargoes);
// Remove non-inheritable roles (list)
if (this.baseRoleGroups.containsKey(UserRole.list))
this.baseRoleGroups.remove(UserRole.list);
} else {
this.baseRoleGroups = new HashMap<UserRole, Set<String>>();
}
if (baseAcls.globalRoleGroups != null)
this.globalRoleGroups = new HashMap<UserRole, Set<String>>(baseAcls.globalRoleGroups);
List<String> embargoes = triples.get(ContentModelHelper.CDRProperty.embargoUntil.toString());
this.extractEmbargoes(embargoes);
for (Entry<String, List<String>> tripleEntry : triples.entrySet()) {
int index = tripleEntry.getKey().indexOf('#');
if (index > 0) {
String namespace = tripleEntry.getKey().substring(0, index + 1);
if (JDOMNamespaceUtil.CDR_ROLE_NS.getURI().equals(namespace)) {
UserRole userRole = UserRole.getUserRole(tripleEntry.getKey());
if (inheritPermissions) {
Set<String> groups = this.baseRoleGroups.get(userRole);
if (groups == null) {
groups = new HashSet<String>(tripleEntry.getValue());
this.baseRoleGroups.put(userRole, groups);
} else
groups.addAll(tripleEntry.getValue());
} else {
Set<String> groups = new HashSet<String>(tripleEntry.getValue());
this.baseRoleGroups.put(userRole, groups);
}
}
}
}
this.ancestorsPublished = baseAcls.isPublished();
List<String> publicationTriples = triples.get(ContentModelHelper.CDRProperty.isPublished.toString());
this.isPublished = publicationTriples == null || "yes".equals(publicationTriples.get(0));
this.ancestorsActive = baseAcls.isActive();
List<String> objectState = triples.get(ContentModelHelper.FedoraProperty.state.toString());
this.isActive = objectState == null || ACTIVE_STATE.equals(objectState.get(0));
this.activeRoleGroups = this.getMergedRoleGroups();
}
@SuppressWarnings("unchecked")
private static void copyRoles(Map<String, ? extends Collection<String>> roles, Map<UserRole, Set<String>> destination) {
if (roles == null)
return;
Iterator<?> roleIt = roles.entrySet().iterator();
while (roleIt.hasNext()) {
Map.Entry<String, Collection<String>> entry = (Map.Entry<String, Collection<String>>) roleIt.next();
UserRole userRole = UserRole.getUserRole(entry.getKey());
if (userRole != null) {
Set<String> groups = new HashSet<String>(entry.getValue());
destination.put(userRole, groups);
}
}
}
private void extractEmbargoes(Collection<String> embargoes) {
if (embargoes != null) {
this.activeEmbargoes = new ArrayList<Date>(embargoes.size());
for (String embargo : embargoes) {
try {
this.activeEmbargoes.add(DateTimeUtil.parseUTCToDate(embargo));
} catch (ParseException e) {
LOG.warn("Failed to parse embargo " + embargo, e);
} catch (IllegalArgumentException e) {
LOG.warn("Failed to parse embargo " + embargo, e);
}
}
}
}
public Map<UserRole, Set<String>> getActiveRoleGroups() {
return this.activeRoleGroups;
}
/**
* Generates a new role/group mapping by filtering out role mappings that do not have administrative viewing rights.
* This is based on if there are any active embargoes, the object is unpublished or not active.
*
* @return
*/
private Map<UserRole, Set<String>> getMergedRoleGroups() {
boolean removePatrons = !this.isPublished() || !this.isActive();
boolean metadataPatrons = false;
if (!removePatrons) {
// Check to see if there are active embargoes, and if there are that their window has not passed
Date lastActiveEmbargo = getLastActiveEmbargoUntilDate();
metadataPatrons = lastActiveEmbargo != null;
}
// Patrons were blocked, remove groups granted to non-admin user rules
if (removePatrons) {
activeRoleGroups = new HashMap<UserRole, Set<String>>();
if (this.baseRoleGroups != null)
for (Map.Entry<UserRole, Set<String>> roleGroups : this.baseRoleGroups.entrySet())
if (roleGroups.getKey().getPermissions().contains(Permission.viewAdminUI))
activeRoleGroups.put(roleGroups.getKey(), roleGroups.getValue());
} else if (metadataPatrons) {
activeRoleGroups = new HashMap<UserRole, Set<String>>();
if (this.baseRoleGroups != null) {
Set<String> metadataGroups = baseRoleGroups.get(UserRole.metadataPatron);
if (metadataGroups == null) {
metadataGroups = new HashSet<String>();
}
for (Map.Entry<UserRole, Set<String>> roleGroups : this.baseRoleGroups.entrySet()) {
if (roleGroups.getKey().getPermissions().contains(Permission.viewAdminUI))
activeRoleGroups.put(roleGroups.getKey(), roleGroups.getValue());
else {
metadataGroups.addAll(roleGroups.getValue());
}
}
if (metadataGroups.size() > 0)
activeRoleGroups.put(UserRole.metadataPatron, metadataGroups);
}
} else {
// Patrons not blocked, return all the grants plus the global roles.
activeRoleGroups = new HashMap<UserRole, Set<String>>(this.baseRoleGroups);
}
if (this.globalRoleGroups != null)
for (Map.Entry<UserRole, Set<String>> roleGroups : this.globalRoleGroups.entrySet())
if (roleGroups.getKey().getPermissions().contains(Permission.viewAdminUI)) {
Set<String> roleGroup = activeRoleGroups.get(roleGroups.getKey());
if (roleGroup == null)
activeRoleGroups.put(roleGroups.getKey(), roleGroups.getValue());
else roleGroup.addAll(roleGroups.getValue());
}
return activeRoleGroups;
}
/**
* Find the last active embargo date, if applicable
*
* @return the embargo date or null
*/
public Date getLastActiveEmbargoUntilDate() {
Date result = null;
if (this.activeEmbargoes != null) {
Date dateNow = new Date();
for (Date embargoDate : this.activeEmbargoes) {
if (embargoDate.after(dateNow)) {
if (result == null || embargoDate.after(result)) {
result = embargoDate;
}
}
}
}
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append("Object Access Controls (").append(object.getPid()).append(")")
.append("\nRoles granted to groups:\n");
for (UserRole r : baseRoleGroups.keySet()) {
result.append(r.getPredicate()).append("\n");
for (String g : baseRoleGroups.get(r)) {
result.append(g).append("\t");
}
}
result.append("\nActive embargo dates:");
if (activeEmbargoes != null) {
for (Date d : activeEmbargoes) {
try {
result.append(DateTimeUtil.formatDateToUTC(d));
} catch (ParseException e) {
LOG.error("Failed to parse date " + d, e);
}
}
}
return result.toString();
}
public PID getObject() {
return object;
}
/**
* Builds a set of all the active user roles granted to the given groups.
*
* @param groups
* @return
*/
public Set<UserRole> getRoles(String[] groups) {
return this.getRoles(groups, this.activeRoleGroups);
}
public Set<UserRole> getBaseRoles(String[] groups) {
return this.getRoles(groups, this.baseRoleGroups);
}
/**
* Builds a set of all the user roles granted to the given groups.
*
* @param groups
* @param roleGroups
* @return
*/
private Set<UserRole> getRoles(String[] groups, Map<UserRole, Set<String>> roleGroups) {
Set<UserRole> result = new HashSet<UserRole>();
for (String group : groups) { // get all user roles
for (UserRole r : roleGroups.keySet()) {
if (roleGroups.get(r).contains(group)) {
result.add(r);
}
}
}
return result;
}
public Set<UserRole> getRoles(AccessGroupSet groups) {
return this.getRoles(groups, this.activeRoleGroups);
}
public Set<UserRole> getBaseRoles(AccessGroupSet groups) {
return this.getRoles(groups, this.baseRoleGroups);
}
private Set<UserRole> getRoles(AccessGroupSet groups, Map<UserRole, Set<String>> roleGroups) {
Set<UserRole> result = new HashSet<UserRole>();
for (String group : groups) { // get all user roles
for (UserRole r : roleGroups.keySet()) {
if (roleGroups.get(r).contains(group)) {
result.add(r);
}
}
}
return result;
}
/**
* Determines if this access object contains roles matching any of the groups in the supplied access group set
*
* @param groups
* group membershps
* @return true if any of the groups are associated with a role for this object
*/
public boolean containsAny(AccessGroupSet groups) {
Map<UserRole, Set<String>> roleGroups = this.activeRoleGroups;
for (String group : groups) {
for (UserRole r : roleGroups.keySet()) {
if (roleGroups.get(r).contains(group)) {
return true;
}
}
}
return false;
}
/**
* Determines if a user has a specific type of permission on this object, given a set of groups.
*
* @param groups
* user memberships
* @param permission
* the permission requested
* @return if permitted
*/
public boolean hasPermission(AccessGroupSet groups, Permission permission) {
Set<UserRole> roles = this.getRoles(groups);
return hasPermission(groups, permission, roles);
}
public static boolean hasPermission(AccessGroupSet groups, Permission permission, Set<UserRole> roles) {
for (UserRole r : roles) {
if (r.getPermissions().contains(permission))
return true;
}
return false;
}
/**
* Determines if a user has a specific type of permission on this object, given a set of groups.
*
* @param groups
* user memberships
* @param permission
* the permission requested
* @return if permitted
*/
public boolean hasPermission(String[] groups, Permission permission) {
Set<UserRole> roles = this.getRoles(groups);
for (UserRole r : roles) {
if (r.getPermissions().contains(permission))
return true;
}
return false;
}
/**
* Retrieves the set of all permissions granted to a set of access groups
*
* @param groups
* @return
*/
public Set<String> getPermissionsByGroups(AccessGroupSet groups) {
Set<String> permissions = new HashSet<String>();
Set<UserRole> roles = this.getRoles(groups);
for (UserRole r : roles) {
for (Permission permission : r.getPermissions())
if (!permissions.contains(permission.name()))
permissions.add(permission.name());
}
return permissions;
}
/**
* Returns all groups assigned to this object that possess the given permission
*
* @param permission
* @return
*/
public Set<String> getGroupsByPermission(Permission permission) {
Set<String> groups = new HashSet<String>();
for (Map.Entry<UserRole, Set<String>> r2g : this.activeRoleGroups.entrySet()) {
if (r2g.getKey().getPermissions().contains(permission)) {
groups.addAll(r2g.getValue());
}
}
return groups;
}
/**
* Returns all groups assigned to the given role
*
* @param userRole
* @return
*/
public Set<String> getGroupsByUserRole(UserRole userRole) {
return this.activeRoleGroups.get(userRole);
}
/**
* Returns a list where each entry contains a single role uri + group pairing assigned to this object. Values are
* pipe delimited
*
* @return
*/
public List<String> roleGroupsToList() {
List<String> result = new ArrayList<String>();
for (Map.Entry<UserRole, Set<String>> r2g : this.activeRoleGroups.entrySet()) {
String roleName = r2g.getKey().getURI().toString();
for (String group : r2g.getValue()) {
result.add(roleName + "|" + group);
}
}
return result;
}
/**
* Returns a list where each entry contains a single role name + group pairing assigned to this object. Values are
* pipe delimited
*
* @return
*/
public List<String> roleGroupsToUnprefixedList() {
List<String> result = new ArrayList<String>();
for (Map.Entry<UserRole, Set<String>> r2g : this.activeRoleGroups.entrySet()) {
String roleName = r2g.getKey().getPredicate().toString();
for (String group : r2g.getValue()) {
result.add(roleName + "|" + group);
}
}
return result;
}
public boolean isPublished() {
return this.ancestorsPublished && (isPublished == null || isPublished);
}
public Boolean getIsPublished() {
return isPublished;
}
public void setIsPublished(Boolean isPublished) {
this.isPublished = isPublished;
}
public boolean isAncestorsPublished() {
return ancestorsPublished;
}
public void setAncestorsPublished(boolean ancestorsPublished) {
this.ancestorsPublished = ancestorsPublished;
}
public boolean isActive() {
return this.ancestorsActive && (isActive == null || isActive);
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public boolean isAncestorsActive() {
return ancestorsActive;
}
public void setAncestorsActive(boolean ancestorsActive) {
this.ancestorsActive = ancestorsActive;
}
}