package edu.harvard.iq.dataverse.search;
import edu.harvard.iq.dataverse.Dataset;
import edu.harvard.iq.dataverse.DatasetVersion;
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseRoleServiceBean;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.DvObjectServiceBean;
import edu.harvard.iq.dataverse.PermissionServiceBean;
import edu.harvard.iq.dataverse.RoleAssigneeServiceBean;
import edu.harvard.iq.dataverse.RoleAssignment;
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
import edu.harvard.iq.dataverse.authorization.groups.Group;
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Named;
/**
* Determine whether items should be searchable.
*/
@Stateless
@Named
public class SearchPermissionsServiceBean {
private static final Logger logger = Logger.getLogger(SearchPermissionsServiceBean.class.getCanonicalName());
@EJB
AuthenticationServiceBean userServiceBean;
@EJB
DvObjectServiceBean dvObjectService;
@EJB
PermissionServiceBean permissionService;
@EJB
RoleAssigneeServiceBean roleAssigneeService;
@EJB
DataverseRoleServiceBean rolesSvc;
@EJB
AuthenticationServiceBean authSvc;
@EJB
GroupServiceBean groupService;
@EJB
SettingsServiceBean settingsService;
LinkedHashMap<String, RoleAssignee> roleAssigneeCache = new LinkedHashMap<>(100, 0.7f, true);
private static final int MAX_CACHE_SIZE = 2000;
/**
* @todo Should we make a PermStrings object? Probably.
*
* @return A list of strings on which Solr will JOIN to enforce permissions
*/
public List<String> findDataversePerms(Dataverse dataverse) {
List<String> permStrings = new ArrayList<>();
if (hasBeenPublished(dataverse)) {
permStrings.add(IndexServiceBean.getPublicGroupString());
}
// permStrings.addAll(findDirectAssignments(dataverse));
// permStrings.addAll(findImplicitAssignments(dataverse));
permStrings.addAll(findDvObjectPerms(dataverse));
return permStrings;
}
public List<String> findDatasetVersionPerms(DatasetVersion version) {
List<String> perms = new ArrayList<>();
if (version.isReleased()) {
perms.add(IndexServiceBean.getPublicGroupString());
}
// perms.addAll(findDirectAssignments(version.getDataset()));
// perms.addAll(findImplicitAssignments(version.getDataset()));
perms.addAll(findDvObjectPerms(version.getDataset()));
return perms;
}
public List<String> findDvObjectPerms(DvObject dvObject) {
List<String> permStrings = new ArrayList<>();
resetRoleAssigneeCache();
Set<RoleAssignment> roleAssignments = rolesSvc.rolesAssignments(dvObject);
for (RoleAssignment roleAssignment : roleAssignments) {
logger.fine("role assignment on dvObject " + dvObject.getId() + ": " + roleAssignment.getAssigneeIdentifier());
if (roleAssignment.getRole().permissions().contains(getRequiredSearchPermission(dvObject))) {
RoleAssignee userOrGroup = getRoleAssignee(roleAssignment.getAssigneeIdentifier());
String indexableUserOrGroupPermissionString = getIndexableStringForUserOrGroup(userOrGroup);
if (indexableUserOrGroupPermissionString != null) {
permStrings.add(indexableUserOrGroupPermissionString);
}
}
}
resetRoleAssigneeCache();
return permStrings;
}
private void resetRoleAssigneeCache() {
roleAssigneeCache.clear();
}
private RoleAssignee getRoleAssignee(String idtf) {
RoleAssignee ra = roleAssigneeCache.get(idtf);
if (ra != null) {
return ra;
}
ra = roleAssigneeService.getRoleAssignee(idtf);
roleAssigneeCache.put(idtf, ra);
if (roleAssigneeCache.size() > MAX_CACHE_SIZE) {
roleAssigneeCache.remove(roleAssigneeCache.keySet().iterator().next());
}
return ra;
}
@Deprecated
private List<String> findDirectAssignments(DvObject dvObject) {
List<String> permStrings = new ArrayList<>();
List<RoleAssignee> roleAssignees = findWhoHasDirectAssignments(dvObject);
for (RoleAssignee roleAssignee : roleAssignees) {
logger.fine("user or group (findDirectAssignments): " + roleAssignee.getIdentifier());
String indexableUserOrGroupPermissionString = getIndexableStringForUserOrGroup(roleAssignee);
if (indexableUserOrGroupPermissionString != null) {
permStrings.add(indexableUserOrGroupPermissionString);
}
}
return permStrings;
}
@Deprecated
private List<RoleAssignee> findWhoHasDirectAssignments(DvObject dvObject) {
List<RoleAssignee> emptyList = new ArrayList<>();
List<RoleAssignee> peopleWhoCanSearch = emptyList;
resetRoleAssigneeCache();
List<RoleAssignment> assignmentsOn = permissionService.assignmentsOn(dvObject);
for (RoleAssignment roleAssignment : assignmentsOn) {
if (roleAssignment.getRole().permissions().contains(getRequiredSearchPermission(dvObject))) {
RoleAssignee userOrGroup = getRoleAssignee(roleAssignment.getAssigneeIdentifier());
if (userOrGroup != null) {
peopleWhoCanSearch.add(userOrGroup);
}
}
}
resetRoleAssigneeCache();
return peopleWhoCanSearch;
}
@Deprecated
private List<String> findImplicitAssignments(DvObject dvObject) {
List<String> permStrings = new ArrayList<>();
DvObject parent = dvObject.getOwner();
while (parent != null) {
if (respectPermissionRoot()) {
if (parent.isEffectivelyPermissionRoot()) {
return permStrings;
}
}
if (parent.isInstanceofDataverse()) {
permStrings.addAll(findDirectAssignments(parent));
} else if (parent.isInstanceofDataset()) {
// files get discoverability from their parent dataset
permStrings.addAll(findDirectAssignments(parent));
}
parent = parent.getOwner();
}
return permStrings;
}
public Map<DatasetVersion.VersionState, Boolean> getDesiredCards(Dataset dataset) {
Map<DatasetVersion.VersionState, Boolean> desiredCards = new LinkedHashMap<>();
DatasetVersion latestVersion = dataset.getLatestVersion();
DatasetVersion.VersionState latestVersionState = latestVersion.getVersionState();
DatasetVersion releasedVersion = dataset.getReleasedVersion();
boolean atLeastOnePublishedVersion = false;
if (releasedVersion != null) {
atLeastOnePublishedVersion = true;
} else {
atLeastOnePublishedVersion = false;
}
if (atLeastOnePublishedVersion == false) {
if (latestVersionState.equals(DatasetVersion.VersionState.DRAFT)) {
desiredCards.put(DatasetVersion.VersionState.DRAFT, true);
desiredCards.put(DatasetVersion.VersionState.DEACCESSIONED, false);
desiredCards.put(DatasetVersion.VersionState.RELEASED, false);
} else if (latestVersionState.equals(DatasetVersion.VersionState.DEACCESSIONED)) {
desiredCards.put(DatasetVersion.VersionState.DEACCESSIONED, true);
desiredCards.put(DatasetVersion.VersionState.RELEASED, false);
desiredCards.put(DatasetVersion.VersionState.DRAFT, false);
} else {
String msg = "No-op. Unexpected condition reached: There is no published version and the latest published version is neither " + DatasetVersion.VersionState.DRAFT + " nor " + DatasetVersion.VersionState.DEACCESSIONED + ". Its state is " + latestVersionState + ".";
logger.info(msg);
}
} else if (atLeastOnePublishedVersion == true) {
if (latestVersionState.equals(DatasetVersion.VersionState.RELEASED)
|| latestVersionState.equals(DatasetVersion.VersionState.DEACCESSIONED)) {
desiredCards.put(DatasetVersion.VersionState.RELEASED, true);
desiredCards.put(DatasetVersion.VersionState.DRAFT, false);
desiredCards.put(DatasetVersion.VersionState.DEACCESSIONED, false);
} else if (latestVersionState.equals(DatasetVersion.VersionState.DRAFT)) {
desiredCards.put(DatasetVersion.VersionState.DRAFT, true);
desiredCards.put(DatasetVersion.VersionState.RELEASED, true);
desiredCards.put(DatasetVersion.VersionState.DEACCESSIONED, false);
} else {
String msg = "No-op. Unexpected condition reached: There is at least one published version but the latest version is neither published nor draft";
logger.info(msg);
}
} else {
String msg = "No-op. Unexpected condition reached: Has a version been published or not?";
logger.info(msg);
}
return desiredCards;
}
private boolean hasBeenPublished(Dataverse dataverse) {
return dataverse.isReleased();
}
private Permission getRequiredSearchPermission(DvObject dvObject) {
if (dvObject.isInstanceofDataverse()) {
return Permission.ViewUnpublishedDataverse;
} else {
return Permission.ViewUnpublishedDataset;
}
}
@Deprecated
private boolean respectPermissionRoot() {
boolean safeDefaultIfKeyNotFound = true;
// see javadoc of the key
return settingsService.isTrueForKey(SettingsServiceBean.Key.SearchRespectPermissionRoot, safeDefaultIfKeyNotFound);
}
/**
* From a Solr perspective we can't just index any string when we go to do
* the JOIN to enforce security. (Maybe putting quotes around the string at
* search time would allow this.) For users, we index the primary key from
* the AuthenticatedUsers table. For groups we index the "alias" which
* should be globally unique because non-builtin groups have a sort of a
* name space with "shib/2" and "ip/ipGroup3", for example.
*/
private String getIndexableStringForUserOrGroup(RoleAssignee userOrGroup) {
if (userOrGroup instanceof AuthenticatedUser) {
logger.fine(userOrGroup.getIdentifier() + " must be a user: " + userOrGroup.getClass().getName());
AuthenticatedUser au = (AuthenticatedUser) userOrGroup;
// Strong prefence to index based on system generated value (e.g. primary key) whenever possible: https://github.com/IQSS/dataverse/issues/1151
Long primaryKey = au.getId();
return IndexServiceBean.getGroupPerUserPrefix() + primaryKey;
} else if (userOrGroup instanceof Group) {
logger.fine(userOrGroup.getIdentifier() + " must be a group: " + userOrGroup.getClass().getName());
Group group = (Group) userOrGroup;
logger.fine("group: " + group.getAlias());
String groupAlias = group.getAlias();
if (groupAlias != null) {
return IndexServiceBean.getGroupPrefix() + groupAlias;
} else {
logger.fine("Could not find group alias for " + group.getIdentifier());
return null;
}
} else {
return null;
}
}
}