forked from DSpace/DSpace
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from dataquest-dev/feature/se-8-create-ACL-ele…
…ment | Phases | MM | MB | MR | JM | Total | |-----------------|----:|----:|-----:|-----:|-------:| | ETA | 20.8 | 0 | 0 | 0 | 0 | | Developing | 7+8+2.5+1 | 0 | 0 | 0 | 0 | | Review | 0 | 0 | 0 | 0 | 0 | | Total | - | - | - | - | 0 | | ETA est. | | | | | 0 | | ETA cust. | - | - | - | - | 0 | ## Problem description The submission can contain admin only fields with fined-grained permissions. ## Problems - I wanted to get the actual context by method _contextService.getContext()_ but it returns null. I found out the context could be obtained from the HttpRequest by method _ContextUtil.obtainCurrentRequestContext()_.
- Loading branch information
Showing
7 changed files
with
535 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/** | ||
* 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.app.util; | ||
|
||
import java.util.Set; | ||
|
||
import org.apache.log4j.Logger; | ||
|
||
/** | ||
* Class that represents single Access Control Entry | ||
* | ||
* @author Michal Josífko | ||
* Class is copied from the LINDAT/CLARIAH-CZ (https://github.com/ufal/clarin-dspace) and modified by | ||
* @author Milan Majchrak (milan.majchrak at dataquest dot sk) | ||
*/ | ||
|
||
public class ACE { | ||
|
||
/** Logger */ | ||
private static final Logger log = Logger.getLogger(ACE.class); | ||
private static final String POLICY_KEYWORD = "policy"; | ||
private static final String POLICY_DENY_KEYWORD = "deny"; | ||
private static final String POLICY_ALLOW_KEYWORD = "allow"; | ||
private static final String ACTION_KEYWORD = "action"; | ||
private static final String ACTION_READ_KEYWORD = "read"; | ||
private static final String ACTION_WRITE_KEYWORD = "write"; | ||
private static final String GRANTEE_TYPE_KEYWORD = "grantee-type"; | ||
private static final String GRANTEE_TYPE_USER_KEYWORD = "user"; | ||
private static final String GRANTEE_TYPE_GROUP_KEYWORD = "group"; | ||
private static final String GRANTEE_ID_KEYWORD = "grantee-id"; | ||
private static final String ANY_KEYWORD = "*"; | ||
public static final int ACTION_READ = 1; | ||
public static final int ACTION_WRITE = 2; | ||
private static final int POLICY_DENY = 1; | ||
private static final int POLICY_ALLOW = 2; | ||
private static final int GRANTEE_TYPE_USER = 1; | ||
private static final int GRANTEE_TYPE_GROUP = 2; | ||
private static final String GRANTEE_ID_ANY = "-1"; | ||
private int policy; | ||
private int action; | ||
private int granteeType; | ||
private String granteeID; | ||
|
||
/** | ||
* Creates new ACE object from given String | ||
* | ||
* @param aceDefinition from the acl definition string | ||
* @return ACE object or null | ||
*/ | ||
public static ACE fromString(String aceDefinition) { | ||
ACE ace = null; | ||
String[] aceParts = aceDefinition.split(","); | ||
|
||
int errors = 0; | ||
|
||
int policy = 0; | ||
int action = 0; | ||
int granteeType = 0; | ||
String granteeID = ""; | ||
|
||
for (int i = 0; i < aceParts.length; i++) { | ||
String acePart = aceParts[i]; | ||
String keyValue[] = acePart.split("="); | ||
|
||
if (keyValue.length != 2) { | ||
log.error("Invalid ACE format: " + acePart); | ||
errors++; | ||
continue; | ||
} | ||
|
||
String key = keyValue[0].trim(); | ||
String value = keyValue[1].trim(); | ||
|
||
if (key.equals(POLICY_KEYWORD)) { | ||
if (value.equals(POLICY_DENY_KEYWORD)) { | ||
policy = POLICY_DENY; | ||
} else if (value.equals(POLICY_ALLOW_KEYWORD)) { | ||
policy = POLICY_ALLOW; | ||
} else { | ||
log.error("Invalid ACE policy value: " + value); | ||
errors++; | ||
} | ||
} else if (key.equals(ACTION_KEYWORD)) { | ||
if (value.equals(ACTION_READ_KEYWORD)) { | ||
action = ACTION_READ; | ||
} else if (value.equals(ACTION_WRITE_KEYWORD)) { | ||
action = ACTION_WRITE; | ||
} else { | ||
log.error("Invalid ACE action value: " + value); | ||
errors++; | ||
} | ||
} else if (key.equals(GRANTEE_TYPE_KEYWORD)) { | ||
if (value.equals(GRANTEE_TYPE_USER_KEYWORD)) { | ||
granteeType = GRANTEE_TYPE_USER; | ||
} else if (value.equals(GRANTEE_TYPE_GROUP_KEYWORD)) { | ||
granteeType = GRANTEE_TYPE_GROUP; | ||
} else { | ||
log.error("Invalid ACE grantee type value: " + value); | ||
errors++; | ||
} | ||
} else if (key.equals(GRANTEE_ID_KEYWORD)) { | ||
if (value.equals(ANY_KEYWORD)) { | ||
granteeID = GRANTEE_ID_ANY; | ||
} else { | ||
granteeID = value; | ||
} | ||
} else { | ||
log.error("Invalid ACE keyword: " + key); | ||
errors++; | ||
} | ||
} | ||
if (errors == 0) { | ||
ace = new ACE(policy, action, granteeType, granteeID); | ||
} | ||
return ace; | ||
} | ||
|
||
/** | ||
* Constructor for creating new Access Control Entry | ||
* | ||
* @param policy deny/allow | ||
* @param action read/write | ||
* @param granteeType user/group | ||
* @param granteeID group UUID | ||
*/ | ||
private ACE(int policy, int action, int granteeType, String granteeID) { | ||
this.policy = policy; | ||
this.action = action; | ||
this.granteeType = granteeType; | ||
this.granteeID = granteeID; | ||
} | ||
|
||
/** | ||
* Method that checks whether the given inputs match this Access Control Entry | ||
* | ||
* @param userID of the current user | ||
* @param groupIDs where is assigned the current user | ||
* @param action could/couldn't be allowed for the user | ||
* @return | ||
*/ | ||
public boolean matches(String userID, Set<String> groupIDs, int action) { | ||
if (this.action == action) { | ||
if (granteeType == ACE.GRANTEE_TYPE_USER) { | ||
if (granteeID.equals(GRANTEE_ID_ANY) || userID.equals(granteeID)) { | ||
return true; | ||
} | ||
} else if (granteeType == ACE.GRANTEE_TYPE_GROUP) { | ||
if (granteeID.equals(GRANTEE_ID_ANY) || groupIDs.contains(granteeID)) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Convenience method to verify if this entry is allowing the action; | ||
* | ||
* @return the action is allowed or not | ||
*/ | ||
public boolean isAllowed() { | ||
return policy == ACE.POLICY_ALLOW; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* 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.app.util; | ||
|
||
import java.sql.SQLException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.UUID; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.apache.log4j.Logger; | ||
import org.dspace.authorize.factory.AuthorizeServiceFactory; | ||
import org.dspace.authorize.service.AuthorizeService; | ||
import org.dspace.core.Context; | ||
import org.dspace.eperson.EPerson; | ||
import org.dspace.eperson.Group; | ||
import org.dspace.eperson.factory.EPersonServiceFactory; | ||
import org.dspace.eperson.service.GroupService; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
* Class that represents Access Control List | ||
* | ||
* @author Michal Josífko | ||
* Class is copied from the LINDAT/CLARIAH-CZ (https://github.com/ufal/clarin-dspace) and modified by | ||
* @author Milan Majchrak (milan.majchrak at dataquest dot sk) | ||
*/ | ||
|
||
@Component | ||
public class ACL { | ||
|
||
/** Logger */ | ||
private static final Logger log = Logger.getLogger(ACL.class); | ||
public static final int ACTION_READ = ACE.ACTION_READ; | ||
public static final int ACTION_WRITE = ACE.ACTION_WRITE; | ||
/** | ||
* List of single Access Control Entry | ||
*/ | ||
private List<ACE> acl; | ||
protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); | ||
protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); | ||
|
||
/** | ||
* Creates new ACL object from given String | ||
* | ||
* @param aclDefinition of the field from the form definition file | ||
* @return ACL object | ||
*/ | ||
public static ACL fromString(String aclDefinition) { | ||
List<ACE> acl = new ArrayList<ACE>(); | ||
if (aclDefinition != null) { | ||
String[] aclEntries = aclDefinition.split(";"); | ||
for (int i = 0; i < aclEntries.length; i++) { | ||
String aclEntry = aclEntries[i]; | ||
ACE ace = ACE.fromString(aclEntry); | ||
if (ace != null) { | ||
acl.add(ace); | ||
} | ||
} | ||
} | ||
return new ACL(acl); | ||
} | ||
|
||
/** | ||
* Constructor for creating new Access Control List | ||
* | ||
* @param acl List of ACE | ||
*/ | ||
ACL(List<ACE> acl) { | ||
this.acl = acl; | ||
} | ||
|
||
/** | ||
* Method to verify whether the the given user ID and set of group IDs is | ||
* allowed to perform the given action | ||
* | ||
* @param userID current user | ||
* @param groupIDs where is assigned the current user | ||
* @param action read/write | ||
* @return if user will see the input field | ||
*/ | ||
private boolean isAllowedAction(String userID, Set<String> groupIDs, int action) { | ||
for (ACE ace : acl) { | ||
if (ace.matches(userID, groupIDs, action)) { | ||
return ace.isAllowed(); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Convenience method to verify whether the current user is allowed to | ||
* perform given action based on current context | ||
* | ||
* @param c Current context, the user information are loaded from the context | ||
* @param action read/write | ||
* @return if user will see the input field | ||
* @throws SQLException | ||
*/ | ||
public boolean isAllowedAction(Context c, int action) { | ||
boolean res = false; | ||
if (acl.isEmpty()) { | ||
// To maintain backwards compatibility allow everything if the ACL | ||
// is empty | ||
return true; | ||
} | ||
try { | ||
if (authorizeService.isAdmin(c)) { | ||
// Admin is always allowed | ||
return true; | ||
} else { | ||
EPerson e = c.getCurrentUser(); | ||
if (e != null) { | ||
UUID userID = e.getID(); | ||
List<Group> groups = groupService.allMemberGroups(c, c.getCurrentUser()); | ||
|
||
Set<String> groupIDs = groups.stream().flatMap(group -> Stream.of(group.getID().toString())) | ||
.collect(Collectors.toSet()); | ||
|
||
return isAllowedAction(userID.toString(), groupIDs, action); | ||
} | ||
} | ||
} catch (SQLException e) { | ||
log.error(e); | ||
} | ||
return res; | ||
} | ||
|
||
/** | ||
* Returns true is the ACL is empty set of rules | ||
* | ||
* @return contains some ACE elements | ||
*/ | ||
public boolean isEmpty() { | ||
return acl.isEmpty(); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.