/*******************************************************************************
* Australian National University Data Commons
* Copyright (C) 2013 The Australian National University
*
* This file is part of Australian National University Data Commons.
*
* Australian National University Data Commons is free software: you
* can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package au.edu.anu.datacommons.security.acl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import au.edu.anu.datacommons.data.db.dao.AclSidDAO;
import au.edu.anu.datacommons.data.db.dao.AclSidDAOImpl;
import au.edu.anu.datacommons.data.db.dao.GenericDAO;
import au.edu.anu.datacommons.data.db.dao.GenericDAOImpl;
import au.edu.anu.datacommons.data.db.model.AclSid;
import au.edu.anu.datacommons.data.db.model.Domains;
import au.edu.anu.datacommons.data.db.model.FedoraObject;
import au.edu.anu.datacommons.data.db.model.Groups;
import au.edu.anu.datacommons.util.Util;
/**
* PermissionService
*
* Australian National University Data Commons
*
* A service to retrieve and update permissions
*
* JUnit Coverage:
* None
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner (GT) Initial
* 0.2 28/08/2012 Genevieve Turner (GT) Updates to fix an exception if there is no acl_sid row for the user
* 0.3 17/09/2012 Genevieve Turner (GT) Added methods around getting a list of groups the user has create permissions on
* 0.4 06/11/2012 Genevieve Turner (GT) Updates to fix an issue where there is a NotFoundException if the user is not the owner or has administrative permissions for the object
* </pre>
*
*/
@Service("permissionService")
public class PermissionService {
static final Logger LOGGER = LoggerFactory.getLogger(PermissionService.class);
@Resource(name="aclService")
MutableAclService aclService;
/**
* getListOfPermission
*
* Retrieves a list of permissions. If the user name is null then it gets a list
* of permissions for the currently logged in user
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner(GT) Initial
* 0.3 17/09/2012 Genevieve Turner (GT) Updated to use the getAuthenticatedSidList method
* </pre>
*
* @param clazz The class type to get permissions for
* @param id The id to get permissions for
* @param username The username to get permissions for
* @return A list of permissions given the constraints
*/
public List<Permission> getListOfPermission(Class<?> clazz, Long id, String username) {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(clazz, id);
// Get the uers permissions
List<Sid> sidList = new ArrayList<Sid>();
if (Util.isNotEmpty(username)) {
LOGGER.trace("Retrieving permissions for username {}", username);
Sid sid = new PrincipalSid(username.toLowerCase());
sidList.add(sid);
}
else {
sidList.addAll(getAuthenticatedSidList());
}
List<Permission> permissionList = new ArrayList<Permission>();
Acl acl = null;
try {
acl = aclService.readAclById(objectIdentity);
}
catch (NotFoundException e) {
LOGGER.error("Issue with permissions", e);
}
// Would have liked to have had this in the previous try/catch but it appears
// to throw an exception when the permission is not found.
if (acl != null) {
for (Permission permission : CustomACLPermission.getPermissionList()) {
try {
if (checkSinglePermission(acl,permission, sidList)) {
permissionList.add(permission);
}
else {
LOGGER.info("Does not have permission {}", permission.getMask());
}
}
catch(NotFoundException e) {
// No op.
}
}
}
return permissionList;
}
/**
* checkSinglePermission
*
* Check if the permission is granted
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param acl The acl to check against
* @param permission The permission to check
* @param sidList The list of sids to check
* @return Whether the sids have this permission
*/
private boolean checkSinglePermission(Acl acl, Permission permission, List<Sid> sidList) {
List<Permission> permissionList = new ArrayList<Permission>();
permissionList.add(permission);
return acl.isGranted(permissionList, sidList, false);
}
/**
* checkViewPermission
*
* Check if the user has permissions to view an object
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param fedoraObject Check the fedora object permissions
* @return Whether the user has permissions
*/
public boolean checkViewPermission(FedoraObject fedoraObject) {
boolean hasPermission = false;
ObjectIdentity objectIdentity = new ObjectIdentityImpl(FedoraObject.class, fedoraObject.getId());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<Sid> sidList = new ArrayList<Sid>();
// Put the user into a sid list
Sid sid = new PrincipalSid(authentication.getName());
sidList.add(sid);
Iterator<GrantedAuthority> it = authentication.getAuthorities().iterator();
while (it.hasNext()) {
GrantedAuthority auth = it.next();
Sid authSid = new GrantedAuthoritySid(auth.getAuthority());
sidList.add(authSid);
}
Acl acl = null;
try {
acl = aclService.readAclById(objectIdentity, sidList);
hasPermission = acl.isGranted(CustomACLPermission.getPermissionList(), sidList, false);
}
catch (NotFoundException e) {
// No op.
}
return hasPermission;
}
/**
* checkPermission
*
* Verify that the user has the given permission
*
* <pre>
* Version Date Developer Description
* X.X 11/12/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param fedoraObject
* @param permission
* @return
*/
public boolean checkPermission(FedoraObject fedoraObject, Permission permission) {
boolean hasPermission = false;
List<Sid> sidList = getAuthenticatedSidList();
ObjectIdentity objectIdentity = new ObjectIdentityImpl(FedoraObject.class, fedoraObject.getId());
Acl acl = null;
try {
acl = aclService.readAclById(objectIdentity, sidList);
hasPermission = acl.isGranted(Arrays.asList(permission), sidList, false);
}
catch (NotFoundException e) {
LOGGER.debug("User doesn't have permissions");
}
return hasPermission;
}
/**
* checkPermission
*
* Check the permission of the object
*
* @param clazz The class of the object
* @param id THe id of the object
* @param permission The permission to check
* @return Indicates whether the logged in user has the given permission
*/
public boolean checkPermission(Class<?> clazz, Long id, Permission permission) {
boolean hasPermission = false;
List<Sid> sidList = getAuthenticatedSidList();
ObjectIdentity objectIdentity = new ObjectIdentityImpl(clazz, id);
Acl acl = null;
try {
acl = aclService.readAclById(objectIdentity, sidList);
hasPermission = acl.isGranted(Arrays.asList(permission), sidList, false);
}
catch (NotFoundException e) {
LOGGER.debug("User doesn't have permissions");
}
return hasPermission;
}
/**
* getCreatePermissions
*
* This method has been created as the Spring Security post filters do not appear to work
* when they are not accessed via Servlet/REST. We need to see where the user can create groups
* for the XSL transformation which is not being called via the Servlet.
*
* <pre>
* Version Date Developer Description
* 0.3 17/09/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @return A list of groups the logged in user has create permissions for
*/
public List<Groups> getCreatePermissions() {
GenericDAO<Groups, Long> groupsDAO = new GenericDAOImpl<Groups, Long>(Groups.class);
List<Groups> groups = groupsDAO.getAll();
List<Sid> sidList = getAuthenticatedSidList();
List<Groups> createGroups = new ArrayList<Groups>();
List<Permission> permissionList = new ArrayList<Permission>();
permissionList.add(CustomACLPermission.WRITE);
permissionList.add(CustomACLPermission.ADMINISTRATION);
for (Groups group : groups) {
if (checkGroup(group, permissionList, sidList)) {
createGroups.add(group);
}
}
return createGroups;
}
/**
* checkGroup
*
* Check the permissions on a group
*
* <pre>
* Version Date Developer Description
* 0.3 17/09/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param group The group to check the permission for
* @param permission The list of permissions to check
* @param sidList The list of sids to check
* @return
*/
private boolean checkGroup(Groups group, List<Permission> permissionList, List<Sid> sidList) {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Groups.class, group.getId());
boolean hasPermission = false;
Acl acl = null;
try {
acl = aclService.readAclById(objectIdentity);
hasPermission = acl.isGranted(permissionList, sidList, false);
}
catch(NotFoundException e) {
}
return hasPermission;
}
/**
* getAuthenticatedSidList
*
* Get a list of sids associated with the authenticated user. i.e. The sid for the user
* and the sid(s) for the users granted authorities.
*
* <pre>
* Version Date Developer Description
* 0.3 17/09/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @return
*/
private List<Sid> getAuthenticatedSidList() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
List<Sid> sidList = new ArrayList<Sid>();
Sid sid = new PrincipalSid(authentication.getName());
sidList.add(sid);
Iterator<GrantedAuthority> it = authentication.getAuthorities().iterator();
while (it.hasNext()) {
GrantedAuthority auth = it.next();
Sid authSid = new GrantedAuthoritySid(auth.getAuthority());
sidList.add(authSid);
}
return sidList;
}
/**
* saveUserPermissions
*
* Update the permissions of the given user for the particular group
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner(GT) Initial
* 0.2 28/08/2012 Genevieve Turner (GT) Updates to fix an exception if there is no acl_sid row for the user
* </pre>
*
* @param id The id to save permissions for
* @param username The username to save permissions for
* @param masks The masks of the permissions to save
*/
public void saveUserPermissionsForGroup(Long id, String username, List<Integer> masks) {
// This code is here due to a exception with Transaction must be running
// On the updateAcl action if the acl_sid row does not exist
// There may be something in the spring security framework that can be used instead
// however at this point in time I am unsure as to what it is.
AclSidDAO aclSidDAO = new AclSidDAOImpl();
AclSid aclSid = aclSidDAO.getAclSidByUsername(username);
if (aclSid == null) {
aclSid = new AclSid();
aclSid.setPrincipal(Boolean.TRUE);
aclSid.setSid(username);
aclSidDAO.create(aclSid);
}
ObjectIdentity objectIdentity = new ObjectIdentityImpl(Groups.class, id);
Sid sid = new PrincipalSid(username);
MutableAcl groupAcl = null;
try {
groupAcl = (MutableAcl)aclService.readAclById(objectIdentity);
List<AccessControlEntry> entries = groupAcl.getEntries();
// Delete all the permissions for the specified user
for (int i = entries.size()-1; i >= 0; i--) {
AccessControlEntry entry = entries.get(i);
if (entry.getSid().equals(sid)) {
groupAcl.deleteAce(i);
}
}
}
catch (NotFoundException nfe) {
groupAcl = aclService.createAcl(objectIdentity);
}
for(Permission permission : CustomACLPermission.getPermissionList()) {
if (masks.contains(permission.getMask())) {
// Add all the permissions in the list
groupAcl.insertAce(groupAcl.getEntries().size(), permission, sid, true);
}
}
aclService.updateAcl(groupAcl);
}
/**
* saveUserPermissions
*
* Assign permissions to the given user for the provided objects
*
* @param clazz The class of the items to save permissions for
* @param ids The id's of the items to save permissions for
* @param idsToRemove The ids of the objects to remove
* @param username The username
* @param permission The permission
*/
public void saveUserPermissions(Class<?> clazz, List<Long> ids, List<Long> idsToRemove, String username, Permission permission) {
AclSidDAO aclSidDAO = new AclSidDAOImpl();
AclSid aclSid = aclSidDAO.getAclSidByUsername(username);
if (aclSid == null) {
aclSid = new AclSid();
aclSid.setPrincipal(Boolean.TRUE);
aclSid.setSid(username);
aclSidDAO.create(aclSid);
}
Sid sid = new PrincipalSid(username);
MutableAcl acl = null;
for (Long id : idsToRemove) {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(clazz, id);
try {
acl = (MutableAcl) aclService.readAclById(objectIdentity);
List<AccessControlEntry> entries = acl.getEntries();
for (int i = entries.size()-1; i >= 0; i--) {
AccessControlEntry entry = entries.get(i);
if (entry.getSid().equals(sid)) {
acl.deleteAce(i);
}
}
aclService.updateAcl(acl);
}
catch (NotFoundException e) {
}
}
for (Long id : ids) {
ObjectIdentity objectIdentity = new ObjectIdentityImpl(clazz, id);
try {
acl = (MutableAcl) aclService.readAclById(objectIdentity);
acl.insertAce(acl.getEntries().size(), permission, sid, true);
aclService.updateAcl(acl);
}
catch (NotFoundException e) {
}
}
}
/**
* saveObjectPermissions
*
* Save the permissions for a fedora object
*
* <pre>
* Version Date Developer Description
* 0.1 20/08/2012 Genevieve Turner(GT) Initial
* 0.4 06/11/2012 Genevieve Turner (GT) Updates to fix an issue where there is a NotFoundException if the user is not the owner or has administrative permissions for the object
* </pre>
*
* @param fedoraObject The fedora object to save permissions for
*/
public void saveObjectPermissions(FedoraObject fedoraObject) {
LOGGER.trace("Saving permissions for pid {}", fedoraObject.getObject_id());
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Sid sid = new PrincipalSid(authentication.getName());
ObjectIdentity group_oi = new ObjectIdentityImpl(Groups.class, fedoraObject.getGroup_id());
MutableAcl groupAcl = null;
try {
groupAcl = (MutableAcl) aclService.readAclById(group_oi);
}
catch (NotFoundException nfe) {
// If the group does not exist in acl_object_identity add the row
groupAcl = aclService.createAcl(group_oi);
}
ObjectIdentity fedora_oi = new ObjectIdentityImpl(FedoraObject.class, fedoraObject.getId());
MutableAcl fedoraAcl = null;
try {
fedoraAcl = (MutableAcl) aclService.readAclById(fedora_oi);
}
catch (NotFoundException nfe) {
// If the object does not exist in acl_object_identity add the row
fedoraAcl = aclService.createAcl(fedora_oi);
}
Sid owner = fedoraAcl.getOwner();
// If the owner is null set all the information
if (owner == null) {
fedoraAcl.setParent(groupAcl);
fedoraAcl.setEntriesInheriting(Boolean.TRUE);
fedoraAcl.setOwner(sid);
}
// If the user has permissions allow them to update the parent
else if (hasSetParentPermissions(fedoraAcl, sid)) {
fedoraAcl.setParent(groupAcl);
}
aclService.updateAcl(fedoraAcl);
}
/**
* hasSetGroupPermissionsForObject
*
* Checks if the currently logged in user can edit permissions for the fedora object
*
* <pre>
* Version Date Developer Description
* 0.4 06/11/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param fedoraObject The fedora object to check
* @return
*/
public boolean hasSetGroupPermissionsForObject(FedoraObject fedoraObject) {
ObjectIdentity fedora_oi = new ObjectIdentityImpl(FedoraObject.class, fedoraObject.getId());
MutableAcl fedoraAcl = null;
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Sid sid = new PrincipalSid(authentication.getName());
try {
fedoraAcl = (MutableAcl) aclService.readAclById(fedora_oi);
}
catch (NotFoundException nfe) {
LOGGER.error("Unable to find fedora object acl");
throw nfe;
}
return hasSetParentPermissions(fedoraAcl, sid);
}
/**
* hasSetParentPermissions
*
* Checks if the sid hs permissions to set the parent for an acl
*
* <pre>
* Version Date Developer Description
* 0.4 06/11/2012 Genevieve Turner(GT) Initial
* </pre>
*
* @param mutableAcl
* @param authSid
* @return
*/
private boolean hasSetParentPermissions(MutableAcl mutableAcl, Sid authSid) {
Sid owner = mutableAcl.getOwner();
if (owner == null || authSid.equals(owner)) {
return true;
}
try {
return mutableAcl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), getAuthenticatedSidList(), false);
}
catch (NotFoundException nfe) {
LOGGER.debug("User is not an administrator");
}
return false;
}
/**
* Initialize the permissions for a domain object. This will create the domain object identity and
* assign READ, WRITE, DELETE, ADMINISTRATION, REVIEW and PUBLISH permissions to the domain for
* the Administrative role.
*
* @param domain The domain to initialize permissions for
*/
public void initializeDomainPermissions(Domains domain) {
ObjectIdentity domain_oi = new ObjectIdentityImpl(Domains.class, domain.getId());
MutableAcl domainAcl = null;
Sid adminSid = new GrantedAuthoritySid("ROLE_ADMIN");
try {
domainAcl = (MutableAcl) aclService.readAclById(domain_oi);
}
catch (NotFoundException nfe) {
// If the domain does not exit in acl_object_identity add the row
domainAcl = aclService.createAcl(domain_oi);
}
domainAcl.setEntriesInheriting(true);
domainAcl.setOwner(adminSid);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.READ, adminSid, true);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.WRITE, adminSid, true);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.DELETE, adminSid, true);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.ADMINISTRATION, adminSid, true);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.REVIEW, adminSid, true);
domainAcl.insertAce(domainAcl.getEntries().size(), CustomACLPermission.PUBLISH, adminSid, true);
aclService.updateAcl(domainAcl);
}
/**
* Initialize the group permissions. This will create the object identity and assign the provided
* domain to be the parent object.
*
* @param group The group to initialize
* @param domain The parent domain
*/
public void initializeGroupPermissions(Groups group, Domains domain) {
ObjectIdentity domain_oi = new ObjectIdentityImpl(Domains.class, domain.getId());
MutableAcl domainAcl = null;
Sid adminSid = new GrantedAuthoritySid("ROLE_ADMIN");
try {
domainAcl = (MutableAcl) aclService.readAclById(domain_oi);
}
catch (NotFoundException nfe) {
// If the domain does not exit in acl_object_identity add the row
domainAcl = aclService.createAcl(domain_oi);
}
ObjectIdentity group_oi = new ObjectIdentityImpl(Groups.class, group.getId());
MutableAcl groupAcl = null;
try {
groupAcl = (MutableAcl) aclService.readAclById(group_oi);
}
catch (NotFoundException nfe) {
groupAcl = aclService.createAcl(group_oi);
}
groupAcl.setEntriesInheriting(true);
groupAcl.setOwner(adminSid);
groupAcl.setParent(domainAcl);
aclService.updateAcl(groupAcl);
}
}