resource-registry/src/main/java/org/gcube/informationsystem/resourceregistry/contexts/security/SecurityContext.java

671 lines
21 KiB
Java

/**
*
*/
package org.gcube.informationsystem.resourceregistry.contexts.security;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.gcube.informationsystem.context.reference.entities.Context;
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
import org.gcube.informationsystem.resourceregistry.contexts.ContextUtility;
import org.gcube.informationsystem.resourceregistry.dbinitialization.DatabaseEnvironment;
import org.gcube.informationsystem.resourceregistry.utils.Utility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.orientechnologies.orient.core.db.ODatabasePool;
import com.orientechnologies.orient.core.db.ODatabaseSession;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordLazySet;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.metadata.security.ORestrictedOperation;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.metadata.security.OSecurity;
import com.orientechnologies.orient.core.metadata.security.OSecurityRole.ALLOW_MODES;
import com.orientechnologies.orient.core.metadata.security.OUser;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class SecurityContext {
private static Logger logger = LoggerFactory.getLogger(SecurityContext.class);
protected static final String DEFAULT_WRITER_ROLE = "writer";
protected static final String DEFAULT_READER_ROLE = "reader";
/*
* H stand for Hierarchical
*/
public static final String H = "H";
protected final boolean hierarchical;
public enum SecurityType {
ROLE("Role"), USER("User");
private final String name;
private SecurityType(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public enum PermissionMode {
READER("Reader"), WRITER("Writer");
private final String name;
private PermissionMode(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
protected final UUID context;
protected final Map<Boolean,Map<PermissionMode,ODatabasePool>> poolMap;
protected SecurityContext parentSecurityContext;
protected Set<SecurityContext> children;
protected boolean isHierarchicalMode() {
return hierarchical && ContextUtility.getHierarchicalMode().get();
}
public void setParentSecurityContext(SecurityContext parentSecurityContext) {
if(this.parentSecurityContext!=null) {
this.parentSecurityContext.getChildren().remove(this);
}
this.parentSecurityContext = parentSecurityContext;
if(parentSecurityContext!=null) {
this.parentSecurityContext.addChild(this);
}
}
public SecurityContext getParentSecurityContext() {
return parentSecurityContext;
}
private void addChild(SecurityContext child) {
this.children.add(child);
}
public Set<SecurityContext> getChildren(){
return this.children;
}
protected ODatabaseDocument getAdminDatabaseDocument() throws ResourceRegistryException {
return ContextUtility.getAdminSecurityContext().getDatabaseDocument(PermissionMode.WRITER);
}
/**
* @return a set containing all children and recursively
* all children.
*/
private Set<SecurityContext> getAllChildren(){
Set<SecurityContext> allChildren = new HashSet<>();
allChildren.add(this);
for(SecurityContext securityContext : getChildren()) {
allChildren.addAll(securityContext.getAllChildren());
}
return allChildren;
}
/**
* @return
*/
private Set<SecurityContext> getAllParents(){
Set<SecurityContext> allParents = new HashSet<>();
SecurityContext parent = getParentSecurityContext();
while(parent!=null) {
allParents.add(parent);
parent = parent.getParentSecurityContext();
}
return allParents;
}
/**
* Use to change the parent not to set the first time
*
* @param newParentSecurityContext
* @param orientGraph
* @throws ResourceRegistryException
*/
public void changeParentSecurityContext(SecurityContext newParentSecurityContext, ODatabaseDocument orientGraph) throws ResourceRegistryException {
if(!hierarchical) {
StringBuilder errorMessage = new StringBuilder();
errorMessage.append("Cannot change parent ");
errorMessage.append(SecurityContext.class.getSimpleName());
errorMessage.append(" to non hierarchic ");
errorMessage.append(SecurityContext.class.getSimpleName());
errorMessage.append(". ");
errorMessage.append(Utility.SHOULD_NOT_OCCUR_ERROR_MESSAGE);
final String error = errorMessage.toString();
logger.error(error);
throw new RuntimeException(error);
}
OSecurity oSecurity = getOSecurity(orientGraph);
Set<SecurityContext> allChildren = getAllChildren();
Set<SecurityContext> oldParents = getAllParents();
Set<SecurityContext> newParents = new HashSet<>();
if(newParentSecurityContext!=null) {
newParents = newParentSecurityContext.getAllParents();
}
/*
* From old parents I remove the new parents so that oldParents
* contains only the parents where I have to remove all
* HReaderRole-UUID e HWriterRole-UUID of allChildren by using
* removeHierarchicRoleFromParent() function
*
*/
oldParents.removeAll(newParents);
removeChildrenHRolesFromParents(oSecurity, oldParents, allChildren);
setParentSecurityContext(newParentSecurityContext);
if(newParentSecurityContext!=null){
for(PermissionMode permissionMode : PermissionMode.values()) {
List<ORole> roles = new ArrayList<>();
for(SecurityContext child : allChildren) {
String roleName = child.getSecurityRoleOrUserName(permissionMode, SecurityType.ROLE, true);
ORole role = oSecurity.getRole(roleName);
roles.add(role);
}
newParentSecurityContext.addHierarchicalRoleToParent(oSecurity, permissionMode, roles.toArray(new ORole[allChildren.size()]));
}
}
}
protected SecurityContext(UUID context, boolean hierarchical) throws ResourceRegistryException {
this.context = context;
this.poolMap = new HashMap<>();
this.hierarchical = hierarchical;
this.children = new HashSet<>();
}
public SecurityContext(UUID context) throws ResourceRegistryException {
this(context, true);
}
private synchronized ODatabasePool getPool(PermissionMode permissionMode, boolean recreate) {
ODatabasePool pool = null;
Boolean h = isHierarchicalMode();
Map<PermissionMode,ODatabasePool> pools = poolMap.get(h);
if(pools == null) {
pools = new HashMap<>();
poolMap.put(h, pools);
} else {
if(recreate) {
pool = pools.get(permissionMode);
if(pool!=null) {
pool.close();
pools.remove(permissionMode);
}
}
}
pool = pools.get(permissionMode);
if(pool == null) {
String username = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, h);
String password = DatabaseEnvironment.DEFAULT_PASSWORDS.get(permissionMode);
pool = new ODatabasePool(DatabaseEnvironment.DB_URI, username, password);
pools.put(permissionMode, pool);
}
return pool;
}
public UUID getUUID() {
return context;
}
public static String getRoleOrUserName(PermissionMode permissionMode, SecurityType securityType) {
return getRoleOrUserName(permissionMode, securityType, false);
}
public static String getRoleOrUserName(PermissionMode permissionMode, SecurityType securityType,
boolean hierarchic) {
StringBuilder stringBuilder = new StringBuilder();
if(hierarchic) {
stringBuilder.append(H);
}
stringBuilder.append(permissionMode);
stringBuilder.append(securityType);
return stringBuilder.toString();
}
public String getSecurityRoleOrUserName(PermissionMode permissionMode, SecurityType securityType,
boolean hierarchic) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(getRoleOrUserName(permissionMode, securityType, hierarchic));
stringBuilder.append("_");
stringBuilder.append(context.toString());
return stringBuilder.toString();
}
private OSecurity getOSecurity(ODatabaseDocument oDatabaseDocument) {
return oDatabaseDocument.getMetadata().getSecurity();
}
public static Set<String> getContexts(OElement element) {
Set<String> contexts = new HashSet<>();
ORecordLazySet oRecordLazySet = element.getProperty(OSecurity.ALLOW_ALL_FIELD);
for (OIdentifiable oIdentifiable : oRecordLazySet) {
ODocument oDocument = (ODocument) oIdentifiable;
String name = oDocument.getProperty("name");
if (name.startsWith(getRoleOrUserName(PermissionMode.WRITER, SecurityType.ROLE))
|| name.startsWith(getRoleOrUserName(PermissionMode.READER, SecurityType.ROLE))) {
String[] list = name.split("_");
if (list.length == 2) {
String contextUUID = list[1];
if (!DatabaseEnvironment.RESERVED_CONTEX_UUID_STRING.contains(contextUUID)) {
contexts.add(contextUUID);
}
}
}
}
return contexts;
}
public void addElement(OElement element) throws ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument adminDatabaseDocument = null;
try {
adminDatabaseDocument = getAdminDatabaseDocument();
addElement(element, adminDatabaseDocument);
}finally {
if(adminDatabaseDocument!=null) {
adminDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
protected void allow(OSecurity oSecurity, ODocument oDocument, boolean hierarchic) {
String writerRoleName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.ROLE, hierarchic);
oSecurity.allowRole(oDocument, ORestrictedOperation.ALLOW_ALL, writerRoleName);
String readerRoleName = getSecurityRoleOrUserName(PermissionMode.READER, SecurityType.ROLE, hierarchic);
oSecurity.allowRole(oDocument, ORestrictedOperation.ALLOW_READ, readerRoleName);
}
public boolean isElementInContext(final OElement element) throws ResourceRegistryException {
ORID orid = element.getIdentity();
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument contextODatabaseDocument = null;
try {
contextODatabaseDocument = getDatabaseDocument(PermissionMode.READER);
ORecord oRecord = contextODatabaseDocument.getRecord(orid);
if(oRecord==null) {
return false;
}
return true;
} finally {
if(contextODatabaseDocument!=null) {
contextODatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
public void addElement(OElement element, ODatabaseDocument oDatabaseDocument) {
ODocument oDocument = element.getRecord();
OSecurity oSecurity = getOSecurity(oDatabaseDocument);
allow(oSecurity, oDocument, false);
if(hierarchical) {
allow(oSecurity, oDocument, true);
}
oDocument.save();
element.save();
}
public void removeElement(OElement element) throws ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument adminDatabaseDocument = null;
try {
adminDatabaseDocument = getAdminDatabaseDocument();
removeElement(element, adminDatabaseDocument);
}finally {
if(adminDatabaseDocument!=null) {
adminDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
protected void deny(OSecurity oSecurity, ODocument oDocument, boolean hierarchical) {
// The element could be created in such a context so the writerUser for the
// context is allowed by default because it was the creator
String writerUserName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.USER, hierarchical);
oSecurity.denyUser(oDocument, ORestrictedOperation.ALLOW_ALL, writerUserName);
String readerUserName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.USER, hierarchical);
oSecurity.denyUser(oDocument, ORestrictedOperation.ALLOW_READ, readerUserName);
String writerRoleName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.ROLE, hierarchical);
oSecurity.denyRole(oDocument, ORestrictedOperation.ALLOW_ALL, writerRoleName);
String readerRoleName = getSecurityRoleOrUserName(PermissionMode.READER, SecurityType.ROLE, hierarchical);
oSecurity.denyRole(oDocument, ORestrictedOperation.ALLOW_READ, readerRoleName);
}
public void removeElement(OElement element, ODatabaseDocument oDatabaseDocument) {
ODocument oDocument = element.getRecord();
OSecurity oSecurity = getOSecurity(oDatabaseDocument);
deny(oSecurity, oDocument, false);
if(hierarchical) {
deny(oSecurity, oDocument, true);
}
oDocument.save();
element.save();
}
protected boolean allowed(final ORole role, final ODocument oDocument) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Boolean> callable = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
ContextUtility.getHierarchicalMode().set(false);
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument oDatabaseDocument = null;
try {
oDatabaseDocument = getDatabaseDocument(PermissionMode.READER);
oDatabaseDocument.activateOnCurrentThread();
ORecord element = oDatabaseDocument.getRecord(oDocument.getIdentity());
if(element == null) {
return false;
}
return true;
} catch(Exception e) {
return false;
} finally {
if(oDatabaseDocument!=null) {
oDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
};
Future<Boolean> result = executor.submit(callable);
try {
return result.get();
} catch(Exception e) {
return false;
}
}
public void create() throws ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument adminDatabaseDocument = null;
try {
adminDatabaseDocument = getAdminDatabaseDocument();
create(adminDatabaseDocument);
adminDatabaseDocument.commit();
} finally {
if(adminDatabaseDocument!=null) {
adminDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
protected ORole addExtraRules(ORole role, PermissionMode permissionMode) {
return role;
}
protected ORole getSuperRole(OSecurity oSecurity, PermissionMode permissionMode) {
String superRoleName = permissionMode.name().toLowerCase();
return oSecurity.getRole(superRoleName);
}
protected void addHierarchicalRoleToParent(OSecurity oSecurity, PermissionMode permissionMode, ORole... roles) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
OUser user = oSecurity.getUser(userName);
for(ORole role : roles) {
user.addRole(role);
}
user.save();
if(getParentSecurityContext() != null) {
getParentSecurityContext().addHierarchicalRoleToParent(oSecurity, permissionMode, roles);
}
}
protected void createRolesAndUsers(OSecurity oSecurity) {
boolean[] booleanArray;
if(hierarchical) {
booleanArray = new boolean[] {false, true};
} else {
booleanArray = new boolean[] {false};
}
for(boolean hierarchical : booleanArray) {
for(PermissionMode permissionMode : PermissionMode.values()) {
ORole superRole = getSuperRole(oSecurity, permissionMode);
String roleName = getSecurityRoleOrUserName(permissionMode, SecurityType.ROLE, hierarchical);
ORole role = oSecurity.createRole(roleName, superRole, ALLOW_MODES.DENY_ALL_BUT);
addExtraRules(role, permissionMode);
role.save();
logger.trace("{} created", role);
if(hierarchical && getParentSecurityContext() != null) {
getParentSecurityContext().addHierarchicalRoleToParent(oSecurity, permissionMode, role);
}
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, hierarchical);
OUser user = oSecurity.createUser(userName, DatabaseEnvironment.DEFAULT_PASSWORDS.get(permissionMode),
role);
user.save();
logger.trace("{} created", user);
}
}
}
public void create(ODatabaseDocument oDatabaseDocument) {
OSecurity oSecurity = getOSecurity(oDatabaseDocument);
createRolesAndUsers(oSecurity);
logger.trace("Security Context (roles and users) with UUID {} successfully created", context.toString());
}
private void drop(OSecurity oSecurity, String name, SecurityType securityType) {
boolean dropped = false;
switch(securityType) {
case ROLE:
dropped = oSecurity.dropRole(name);
break;
case USER:
dropped = oSecurity.dropUser(name);
break;
default:
break;
}
if(dropped) {
logger.trace("{} successfully dropped", name);
} else {
logger.error("{} was not dropped successfully", name);
}
}
public void delete() throws ResourceRegistryException {
ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
ODatabaseDocument adminDatabaseDocument = null;
try {
adminDatabaseDocument = getAdminDatabaseDocument();
delete(adminDatabaseDocument);
adminDatabaseDocument.commit();
} finally {
if(adminDatabaseDocument!=null) {
adminDatabaseDocument.close();
}
if(current!=null) {
current.activateOnCurrentThread();
}
}
}
protected void removeChildrenHRolesFromParents(OSecurity oSecurity) {
Set<SecurityContext> parents = getAllParents();
Set<SecurityContext> allChildren = getAllChildren();
removeChildrenHRolesFromParents(oSecurity, parents, allChildren);
}
protected void removeChildrenHRolesFromParents(OSecurity oSecurity, Set<SecurityContext> parents, Set<SecurityContext> children) {
for(SecurityContext parent : parents) {
parent.removeChildrenHRolesFromMyHUsers(oSecurity, children);
}
}
protected void removeChildrenHRolesFromMyHUsers(OSecurity oSecurity, Set<SecurityContext> children) {
for(PermissionMode permissionMode : PermissionMode.values()) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
OUser user = oSecurity.getUser(userName);
for(SecurityContext child : children) {
String roleName = child.getSecurityRoleOrUserName(permissionMode, SecurityType.ROLE, true);
logger.debug("Going to remove {} from {}", roleName, userName);
boolean removed = user.removeRole(roleName);
logger.trace("{} {} removed from {}", roleName, removed ? "successfully" : "NOT", userName);
}
user.save();
}
}
protected void removeHierarchicRoleFromMyHUser(OSecurity oSecurity, PermissionMode permissionMode, String roleName) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
OUser user = oSecurity.getUser(userName);
logger.debug("Going to remove {} from {}", roleName, userName);
boolean removed = user.removeRole(roleName);
logger.trace("{} {} removed from {}", roleName, removed ? "successfully" : "NOT", userName);
user.save();
}
protected void deleteRolesAndUsers(OSecurity oSecurity) {
boolean[] booleanArray;
if(hierarchical) {
booleanArray = new boolean[] {false, true};
} else {
booleanArray = new boolean[] {false};
}
for(boolean hierarchic : booleanArray) {
if(hierarchic) {
removeChildrenHRolesFromParents(oSecurity);
}
for(PermissionMode permissionMode : PermissionMode.values()) {
for(SecurityType securityType : SecurityType.values()) {
String name = getSecurityRoleOrUserName(permissionMode, securityType, hierarchic);
drop(oSecurity, name, securityType);
}
}
}
}
public void delete(ODatabaseDocument orientGraph) {
OSecurity oSecurity = getOSecurity(orientGraph);
delete(oSecurity);
}
private void delete(OSecurity oSecurity) {
logger.trace("Going to remove Security Context (roles and users) with UUID {}", context.toString());
deleteRolesAndUsers(oSecurity);
logger.trace("Security Context (roles and users) with UUID {} successfully removed", context.toString());
}
public ODatabaseDocument getDatabaseDocument(PermissionMode permissionMode) throws ResourceRegistryException {
try {
ODatabasePool oDatabasePool = getPool(permissionMode, false);
ODatabaseSession oDatabaseSession = null;
try {
oDatabaseSession = oDatabasePool.acquire();
if(oDatabaseSession.isClosed()) {
// Enforcing pool recreation
throw new Exception();
}
}catch (Exception e) {
oDatabasePool = getPool(permissionMode, true);
oDatabaseSession = oDatabasePool.acquire();
}
oDatabaseSession.activateOnCurrentThread();
return oDatabaseSession;
}catch (Exception e) {
throw new ResourceRegistryException(e);
}
}
@Override
public String toString() {
return String.format("%s %s", Context.NAME, getUUID().toString());
}
}