/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.authorize; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.dspace.authorize.service.AuthorizeService; import org.dspace.authorize.service.ResourcePolicyService; import org.dspace.content.*; import org.dspace.content.Collection; import org.dspace.content.factory.ContentServiceFactory; import org.dspace.content.service.BitstreamService; import org.dspace.content.service.WorkspaceItemService; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.eperson.service.GroupService; import org.dspace.workflow.WorkflowItemService; import org.springframework.beans.factory.annotation.Autowired; import java.sql.SQLException; import java.util.*; /** * AuthorizeManager handles all authorization checks for DSpace. For better * security, DSpace assumes that you do not have the right to do something * unless that permission is spelled out somewhere. That "somewhere" is the * ResourcePolicy table. The AuthorizeManager is given a user, an object, and an * action, and it then does a lookup in the ResourcePolicy table to see if there * are any policies giving the user permission to do that action. * <p> * ResourcePolicies now apply to single objects (such as submit (ADD) permission * to a collection.) * <p> * Note: If an eperson is a member of the administrator group (id 1), then they * are automatically given permission for all requests another special group is * group 0, which is anonymous - all EPeople are members of group 0. */ public class AuthorizeServiceImpl implements AuthorizeService { @Autowired(required = true) protected BitstreamService bitstreamService; @Autowired(required = true) protected ContentServiceFactory serviceFactory; @Autowired(required = true) protected GroupService groupService; @Autowired(required = true) protected ResourcePolicyService resourcePolicyService; @Autowired(required = true) protected WorkspaceItemService workspaceItemService; @Autowired(required = true) protected WorkflowItemService workflowItemService; protected AuthorizeServiceImpl() { } @Override public void authorizeAnyOf(Context c, DSpaceObject o, int[] actions)throws AuthorizeException, SQLException { AuthorizeException ex = null; for (int action : actions) { try { authorizeAction(c, o, action); return; } catch (AuthorizeException e) { if (ex == null) { ex = e; } } } throw ex; } @Override public void authorizeAction(Context c, DSpaceObject o, int action) throws AuthorizeException, SQLException { authorizeAction(c, o, action, true); } @Override public void authorizeAction(Context c, DSpaceObject o, int action, boolean useInheritance) throws AuthorizeException, SQLException { authorizeAction(c, c.getCurrentUser(), o, action, useInheritance); } @Override public void authorizeAction(Context c, EPerson e, DSpaceObject o, int action, boolean useInheritance) throws AuthorizeException, SQLException { if (o == null) { // action can be -1 due to a null entry String actionText; if (action == -1) { actionText = "null"; } else { actionText = Constants.actionText[action]; } UUID userid; if (e == null) { userid = null; } else { userid = e.getID(); } throw new AuthorizeException( "Authorization attempted on null DSpace object " + actionText + " by user " + userid); } if (!authorize(c, o, action, e, useInheritance)) { // denied, assemble and throw exception int otype = o.getType(); UUID oid = o.getID(); UUID userid; if (e == null) { userid = null; } else { userid = e.getID(); } // AuthorizeException j = new AuthorizeException("Denied"); // j.printStackTrace(); // action can be -1 due to a null entry String actionText; if (action == -1) { actionText = "null"; } else { actionText = Constants.actionText[action]; } throw new AuthorizeException("Authorization denied for action " + actionText + " on " + Constants.typeText[otype] + ":" + oid + " by user " + userid, o, action); } } @Override public boolean authorizeActionBoolean(Context c, DSpaceObject o, int a) throws SQLException { return authorizeActionBoolean(c, o, a, true); } @Override public boolean authorizeActionBoolean(Context c, DSpaceObject o, int a, boolean useInheritance) throws SQLException { boolean isAuthorized = true; if (o == null) { return false; } try { authorizeAction(c, o, a, useInheritance); } catch (AuthorizeException e) { isAuthorized = false; } return isAuthorized; } @Override public boolean authorizeActionBoolean(Context c, EPerson e, DSpaceObject o, int a, boolean useInheritance) throws SQLException { boolean isAuthorized = true; if (o == null) { return false; } try { authorizeAction(c, e, o, a, useInheritance); } catch (AuthorizeException ex) { isAuthorized = false; } return isAuthorized; } /** * Check to see if the given user can perform the given action on the given * object. Always returns true if the ignore authorization flat is set in * the current context. * * @param c * current context. User is irrelevant; "ignore authorization" * flag is relevant * @param o * object action is being attempted on * @param action * ID of action being attempted, from * <code>org.dspace.core.Constants</code> * @param e * user attempting action * @param useInheritance * flag to say if ADMIN action on the current object or parent * object can be used * @return <code>true</code> if user is authorized to perform the given * action, <code>false</code> otherwise * @throws SQLException if database error */ protected boolean authorize(Context c, DSpaceObject o, int action, EPerson e, boolean useInheritance) throws SQLException { // return FALSE if there is no DSpaceObject if (o == null) { return false; } // is authorization disabled for this context? if (c.ignoreAuthorization()) { return true; } // is eperson set? if not, userToCheck = null (anonymous) EPerson userToCheck = null; if (e != null) { userToCheck = e; // perform isAdmin check to see // if user is an Admin on this object DSpaceObject adminObject = useInheritance ? serviceFactory.getDSpaceObjectService(o).getAdminObject(c, o, action) : null; if (isAdmin(c, adminObject)) { return true; } } // In case the dso is an bundle or bitstream we must ignore custom // policies if it does not belong to at least one installed item (see // DS-2614). // In case the dso is an item and a corresponding workspace or workflow // item exist, we have to ignore custom policies (see DS-2614). boolean ignoreCustomPolicies = false; if (o instanceof Bitstream) { Bitstream b = (Bitstream) o; // Ensure that this is not a collection or community logo DSpaceObject parent = bitstreamService.getParentObject(c, b); if (!(parent instanceof Collection) && !(parent instanceof Community)) { ignoreCustomPolicies = !isAnyItemInstalled(c, b.getBundles()); } } if (o instanceof Bundle) { ignoreCustomPolicies = !isAnyItemInstalled(c, Arrays.asList(((Bundle) o))); } if (o instanceof Item) { if (workspaceItemService.findByItem(c, (Item) o) != null || workflowItemService.findByItem(c, (Item) o) != null) { ignoreCustomPolicies = true; } } for (ResourcePolicy rp : getPoliciesActionFilter(c, o, action)) { if (ignoreCustomPolicies && ResourcePolicy.TYPE_CUSTOM.equals(rp.getRpType())) { continue; } // check policies for date validity if (resourcePolicyService.isDateValid(rp)) { if (rp.getEPerson() != null && rp.getEPerson().equals(userToCheck)) { return true; // match } if ((rp.getGroup() != null) && (groupService.isMember(c, rp.getGroup()))) { // group was set, and eperson is a member // of that group return true; } } } // default authorization is denial return false; } // check whether any bundle belongs to any item that passed submission // and workflow process protected boolean isAnyItemInstalled(Context ctx, List<Bundle> bundles) throws SQLException { for (Bundle bundle : bundles) { for (Item item : bundle.getItems()) { if (workspaceItemService.findByItem(ctx, item) == null && workflowItemService.findByItem(ctx, item) == null) { return true; } } } return false; } /////////////////////////////////////////////// // admin check methods /////////////////////////////////////////////// @Override public boolean isAdmin(Context c, DSpaceObject o) throws SQLException { // return true if user is an Administrator if (isAdmin(c)) { return true; } if (o == null) { return false; } // // First, check all Resource Policies directly on this object // List<ResourcePolicy> policies = getPoliciesActionFilter(c, o, Constants.ADMIN); for (ResourcePolicy rp : policies) { // check policies for date validity if (resourcePolicyService.isDateValid(rp)) { if (rp.getEPerson() != null && rp.getEPerson().equals(c.getCurrentUser())) { return true; // match } if ((rp.getGroup() != null) && (groupService.isMember(c, rp.getGroup()))) { // group was set, and eperson is a member // of that group return true; } } } // If user doesn't have specific Admin permissions on this object, // check the *parent* objects of this object. This allows Admin // permissions to be inherited automatically (e.g. Admin on Community // is also an Admin of all Collections/Items in that Community) DSpaceObject parent = serviceFactory.getDSpaceObjectService(o).getParentObject(c, o); if (parent != null) { return isAdmin(c, parent); } return false; } @Override public boolean isAdmin(Context c) throws SQLException { // if we're ignoring authorization, user is member of admin if (c.ignoreAuthorization()) { return true; } EPerson e = c.getCurrentUser(); if (e == null) { return false; // anonymous users can't be admins.... } else { return groupService.isMember(c, Group.ADMIN); } } public boolean isCommunityAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); if (e != null) { List<ResourcePolicy> policies = resourcePolicyService.find(c, e, groupService.allMemberGroups(c, e), Constants.ADMIN, Constants.COMMUNITY); if (CollectionUtils.isNotEmpty(policies)) { return true; } } return false; } public boolean isCollectionAdmin(Context c) throws SQLException { EPerson e = c.getCurrentUser(); if (e != null) { List<ResourcePolicy> policies = resourcePolicyService.find(c, e, groupService.allMemberGroups(c, e), Constants.ADMIN, Constants.COLLECTION); if (CollectionUtils.isNotEmpty(policies)) { return true; } } return false; } /////////////////////////////////////////////// // policy manipulation methods /////////////////////////////////////////////// @Override public void addPolicy(Context c, DSpaceObject o, int actionID, EPerson e) throws SQLException, AuthorizeException { addPolicy(c, o, actionID, e, null); } @Override public void addPolicy(Context context, DSpaceObject o, int actionID, EPerson e, String type) throws SQLException, AuthorizeException { createResourcePolicy(context, o, null, e, actionID, type); } @Override public void addPolicy(Context c, DSpaceObject o, int actionID, Group g) throws SQLException, AuthorizeException { createResourcePolicy(c, o, g, null, actionID, null); } @Override public void addPolicy(Context c, DSpaceObject o, int actionID, Group g, String type) throws SQLException, AuthorizeException { createResourcePolicy(c, o, g, null, actionID, type); } @Override public List<ResourcePolicy> getPolicies(Context c, DSpaceObject o) throws SQLException { return resourcePolicyService.find(c, o); } @Override public List<ResourcePolicy> findPoliciesByDSOAndType(Context c, DSpaceObject o, String type) throws SQLException { return resourcePolicyService.find(c, o, type); } @Override public List<ResourcePolicy> getPoliciesForGroup(Context c, Group g) throws SQLException { return resourcePolicyService.find(c, g); } @Override public List<ResourcePolicy> getPoliciesActionFilter(Context c, DSpaceObject o, int actionID) throws SQLException { return resourcePolicyService.find(c, o, actionID); } @Override public void inheritPolicies(Context c, DSpaceObject src, DSpaceObject dest) throws SQLException, AuthorizeException { // find all policies for the source object List<ResourcePolicy> policies = getPolicies(c, src); //Only inherit non-ADMIN policies (since ADMIN policies are automatically inherited) List<ResourcePolicy> nonAdminPolicies = new ArrayList<ResourcePolicy>(); for (ResourcePolicy rp : policies) { if (rp.getAction() != Constants.ADMIN) { nonAdminPolicies.add(rp); } } addPolicies(c, nonAdminPolicies, dest); } @Override public void switchPoliciesAction(Context context, DSpaceObject dso, int fromAction, int toAction) throws SQLException, AuthorizeException { List<ResourcePolicy> rps = getPoliciesActionFilter(context, dso, fromAction); for (ResourcePolicy rp : rps) { rp.setAction(toAction); } resourcePolicyService.update(context, rps); } @Override public void addPolicies(Context c, List<ResourcePolicy> policies, DSpaceObject dest) throws SQLException, AuthorizeException { // now add them to the destination object List<ResourcePolicy> newPolicies = new LinkedList<>(); for (ResourcePolicy srp : policies) { ResourcePolicy rp = resourcePolicyService.create(c); // copy over values rp.setdSpaceObject(dest); rp.setAction(srp.getAction()); rp.setEPerson(srp.getEPerson()); rp.setGroup(srp.getGroup()); rp.setStartDate(srp.getStartDate()); rp.setEndDate(srp.getEndDate()); rp.setRpName(srp.getRpName()); rp.setRpDescription(srp.getRpDescription()); rp.setRpType(srp.getRpType()); // and add policy to list of new policies newPolicies.add(rp); } resourcePolicyService.update(c, newPolicies); } @Override public void removeAllPolicies(Context c, DSpaceObject o) throws SQLException, AuthorizeException { resourcePolicyService.removeAllPolicies(c, o); } @Override public void removeAllPoliciesByDSOAndTypeNotEqualsTo(Context c, DSpaceObject o, String type) throws SQLException, AuthorizeException { resourcePolicyService.removeDsoAndTypeNotEqualsToPolicies(c, o, type); } @Override public void removeAllPoliciesByDSOAndType(Context c, DSpaceObject o, String type) throws SQLException, AuthorizeException { resourcePolicyService.removePolicies(c, o, type); } @Override public void removePoliciesActionFilter(Context context, DSpaceObject dso, int actionID) throws SQLException, AuthorizeException { resourcePolicyService.removePolicies(context, dso, actionID); } @Override public void removeGroupPolicies(Context c, Group group) throws SQLException { resourcePolicyService.removeGroupPolicies(c, group); } @Override public void removeGroupPolicies(Context c, DSpaceObject o, Group g) throws SQLException, AuthorizeException { resourcePolicyService.removeDsoGroupPolicies(c, o, g); } @Override public void removeEPersonPolicies(Context c, DSpaceObject o, EPerson e) throws SQLException, AuthorizeException { resourcePolicyService.removeDsoEPersonPolicies(c, o, e); } @Override public List<Group> getAuthorizedGroups(Context c, DSpaceObject o, int actionID) throws java.sql.SQLException { List<ResourcePolicy> policies = getPoliciesActionFilter(c, o, actionID); List<Group> groups = new ArrayList<Group>(); for (ResourcePolicy resourcePolicy : policies) { if (resourcePolicy.getGroup() != null) { groups.add(resourcePolicy.getGroup()); } } return groups; } @Override public boolean isAnIdenticalPolicyAlreadyInPlace(Context c, DSpaceObject o, ResourcePolicy rp) throws SQLException { return isAnIdenticalPolicyAlreadyInPlace(c, o, rp.getGroup(), rp.getAction(), rp.getID()); } @Override public boolean isAnIdenticalPolicyAlreadyInPlace(Context c, DSpaceObject dso, Group group, int action, int policyID) throws SQLException { return !resourcePolicyService.findByTypeGroupActionExceptId(c, dso, group, action, policyID).isEmpty(); } @Override public ResourcePolicy findByTypeGroupAction(Context c, DSpaceObject dso, Group group, int action) throws SQLException { List<ResourcePolicy> policies = resourcePolicyService.find(c, dso, group, action); if (CollectionUtils.isNotEmpty(policies)) { return policies.iterator().next(); } else { return null; } } /** * Generate Policies policies READ for the date in input adding reason. New policies are assigned automatically at the groups that * have right on the collection. E.g., if the anonymous can access the collection policies are assigned to anonymous. * * @param context * The relevant DSpace Context. * @param embargoDate * embargo end date * @param reason * embargo reason * @param dso * DSpace object * @param owningCollection * collection to get group policies from * @throws SQLException if database error * @throws AuthorizeException if authorization error */ @Override public void generateAutomaticPolicies(Context context, Date embargoDate, String reason, DSpaceObject dso, Collection owningCollection) throws SQLException, AuthorizeException { if (embargoDate != null || (embargoDate == null && dso instanceof Bitstream)) { List<Group> authorizedGroups = getAuthorizedGroups(context, owningCollection, Constants.DEFAULT_ITEM_READ); removeAllPoliciesByDSOAndType(context, dso, ResourcePolicy.TYPE_CUSTOM); // look for anonymous boolean isAnonymousInPlace = false; for (Group g : authorizedGroups) { if (StringUtils.equals(g.getName(), Group.ANONYMOUS)) { isAnonymousInPlace = true; } } if (!isAnonymousInPlace) { // add policies for all the groups for (Group g : authorizedGroups) { ResourcePolicy rp = createOrModifyPolicy(null, context, null, g, null, embargoDate, Constants.READ, reason, dso); if (rp != null) resourcePolicyService.update(context, rp); } } else { // add policy just for anonymous ResourcePolicy rp = createOrModifyPolicy(null, context, null, groupService.findByName(context, Group.ANONYMOUS), null, embargoDate, Constants.READ, reason, dso); if (rp != null) resourcePolicyService.update(context, rp); } } } @Override public ResourcePolicy createResourcePolicy(Context context, DSpaceObject dso, Group group, EPerson eperson, int type, String rpType) throws SQLException, AuthorizeException { if (group == null && eperson == null) { throw new IllegalArgumentException("We need at least an eperson or a group in order to create a resource policy."); } ResourcePolicy myPolicy = resourcePolicyService.create(context); myPolicy.setdSpaceObject(dso); myPolicy.setAction(type); myPolicy.setGroup(group); myPolicy.setEPerson(eperson); myPolicy.setRpType(rpType); resourcePolicyService.update(context, myPolicy); return myPolicy; } @Override public ResourcePolicy createOrModifyPolicy(ResourcePolicy policy, Context context, String name, Group group, EPerson ePerson, Date embargoDate, int action, String reason, DSpaceObject dso) throws AuthorizeException, SQLException { ResourcePolicy policyTemp = null; if (policy != null) { List<ResourcePolicy> duplicates = resourcePolicyService.findByTypeGroupActionExceptId(context, dso, group, action, policy.getID()); if (!duplicates.isEmpty()) { policy = duplicates.get(0); } } else { // if an identical policy (same Action and same Group) is already in place modify it... policyTemp = findByTypeGroupAction(context, dso, group, action); } if (policyTemp != null) { policy = policyTemp; policy.setRpType(ResourcePolicy.TYPE_CUSTOM); } if (policy == null) { policy = createResourcePolicy(context, dso, group, ePerson, action, ResourcePolicy.TYPE_CUSTOM); } policy.setGroup(group); policy.setEPerson(ePerson); if (embargoDate != null) { policy.setStartDate(embargoDate); } else { policy.setStartDate(null); policy.setEndDate(null); } policy.setRpName(name); policy.setRpDescription(reason); return policy; } }