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

654 lines
20 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.contexts.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.requests.RequestUtility;
import org.gcube.informationsystem.resourceregistry.utils.DBUtility;
import org.gcube.informationsystem.utils.UUIDManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.arcadedb.database.Document;
import com.arcadedb.database.Identifiable;
import com.arcadedb.database.MutableDocument;
import com.arcadedb.database.RID;
import com.arcadedb.database.Record;
import com.arcadedb.remote.RemoteDatabase;
import com.orientechnologies.orient.core.db.ODatabasePool;
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.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,RemoteDatabase>> databasesMap;
protected SecurityContext parentSecurityContext;
protected Set<SecurityContext> children;
protected boolean isHierarchicalMode() {
return hierarchical && RequestUtility.getRequestInfo().get().isHierarchicalMode();
}
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 RemoteDatabase getAdminDatabaseDocument() throws ResourceRegistryException {
return ContextUtility.getAdminSecurityContext().getRemoteDatabase(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, RemoteDatabase database) 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(DBUtility.SHOULD_NOT_OCCUR_ERROR_MESSAGE);
final String error = errorMessage.toString();
logger.error(error);
throw new RuntimeException(error);
}
// OSecurity oSecurity = getOSecurity(database);
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(database, oldParents, allChildren);
setParentSecurityContext(newParentSecurityContext);
if(newParentSecurityContext!=null){
for(PermissionMode permissionMode : PermissionMode.values()) {
List<Role> roles = new ArrayList<>();
for(SecurityContext child : allChildren) {
String roleName = child.getSecurityRoleOrUserName(permissionMode, SecurityType.ROLE, true);
Role role = database.getRole(roleName);
roles.add(role);
}
newParentSecurityContext.addHierarchicalRoleToParent(database, permissionMode, roles.toArray(new Role[allChildren.size()]));
}
}
}
protected SecurityContext(UUID context, boolean hierarchical) throws ResourceRegistryException {
this.context = context;
this.databasesMap = new HashMap<>();
this.hierarchical = hierarchical;
this.children = new HashSet<>();
}
public SecurityContext(UUID context) throws ResourceRegistryException {
this(context, true);
}
private synchronized RemoteDatabase getPool(PermissionMode permissionMode, boolean recreate) {
RemoteDatabase db = null;
Boolean h = isHierarchicalMode();
Map<PermissionMode,RemoteDatabase> databases = databasesMap.get(h);
if(databases == null) {
databases = new HashMap<>();
databasesMap.put(h, databases);
} else {
if(recreate) {
db = databases.get(permissionMode);
if(db!=null) {
db.close();
databases.remove(permissionMode);
}
}
}
db = databases.get(permissionMode);
if(db == null) {
String username = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, h);
String password = DatabaseEnvironment.DEFAULT_PASSWORDS.get(permissionMode);
db = new RemoteDatabase(DatabaseEnvironment.HOST, DatabaseEnvironment.PORT, DatabaseEnvironment.DB ,username, password);
databases.put(permissionMode, db);
}
return db;
}
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();
}
public static Set<String> getContexts(Document element) {
Set<String> contexts = new HashSet<>();
ORecordLazySet oRecordLazySet = element.getProperty(OSecurity.ALLOW_ALL_FIELD);
for (Identifiable identifiable : oRecordLazySet) {
Document oDocument = (Document) identifiable;
String name = oDocument.getString("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 (!UUIDManager.getInstance().isReservedUUID(contextUUID)) {
contexts.add(contextUUID);
}
}
}
}
return contexts;
}
public void addElement(Document element) throws ResourceRegistryException {
RemoteDatabase current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase adminDatabase = null;
try {
adminDatabase = getAdminDatabaseDocument();
addElement(element, adminDatabase);
}finally {
if(adminDatabase!=null) {
adminDatabase.close();
}
// if(current!=null) {
// current.activateOnCurrentThread();
// }
}
}
protected void allow(RemoteDatabase database, Document document, boolean hierarchic) {
String writerRoleName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.ROLE, hierarchic);
oSecurity.allowRole(document, RestrictedOperation.ALLOW_ALL, writerRoleName);
String readerRoleName = getSecurityRoleOrUserName(PermissionMode.READER, SecurityType.ROLE, hierarchic);
oSecurity.allowRole(document, RestrictedOperation.ALLOW_READ, readerRoleName);
}
public boolean isElementInContext(final Document element) throws ResourceRegistryException {
RID rid = element.getIdentity();
RemoteDatabase current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase database = null;
try {
database = getRemoteDatabase(PermissionMode.READER);
return database.existsRecord(rid);
} finally {
if(database!=null) {
database.close();
}
// if(current!=null) {
// current.activateOnCurrentThread();
// }
}
}
public void addElement(Document document, RemoteDatabase database) {
allow(database, document, false);
if(hierarchical) {
allow(database, document, true);
}
// document.save();
}
public void removeElement(Document document) throws ResourceRegistryException {
RemoteDatabase current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase adminDatabase = null;
try {
adminDatabase = getAdminDatabaseDocument();
removeElement(document, adminDatabase);
}finally {
if(adminDatabase!=null) {
adminDatabase.close();
}
// if(current!=null) {
// current.activateOnCurrentThread();
// }
}
}
protected void deny(RemoteDatabase database, Document document, 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(document, RestrictedOperation.ALLOW_ALL, writerUserName);
String readerUserName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.USER, hierarchical);
oSecurity.denyUser(document, RestrictedOperation.ALLOW_READ, readerUserName);
String writerRoleName = getSecurityRoleOrUserName(PermissionMode.WRITER, SecurityType.ROLE, hierarchical);
oSecurity.denyRole(document, RestrictedOperation.ALLOW_ALL, writerRoleName);
String readerRoleName = getSecurityRoleOrUserName(PermissionMode.READER, SecurityType.ROLE, hierarchical);
oSecurity.denyRole(document, RestrictedOperation.ALLOW_READ, readerRoleName);
}
public void removeElement(Document document, RemoteDatabase database) {
deny(database, document, false);
if(hierarchical) {
deny(database, document, true);
}
// document.save();
}
protected boolean allowed(final ORole role, final Document document) {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Boolean> callable = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
RequestUtility.getRequestInfo().get().setHierarchicalMode(false);
RemoteDatabase current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase database = null;
try {
database = getRemoteDatabase(PermissionMode.READER);
// database.activateOnCurrentThread();
return database.existsRecord(document.getIdentity());
} catch(Exception e) {
return false;
} finally {
if(database!=null) {
database.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 {
RemoteDatabase current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase adminDatabase = null;
try {
adminDatabase = getAdminDatabaseDocument();
create(adminDatabase);
adminDatabase.commit();
} finally {
if(adminDatabase!=null) {
adminDatabase.close();
}
// if(current!=null) {
// current.activateOnCurrentThread();
// }
}
}
protected Role addExtraRules(Role role, PermissionMode permissionMode) {
return role;
}
protected Role getSuperRole(RemoteDatabase database, PermissionMode permissionMode) {
String superRoleName = permissionMode.name().toLowerCase();
return database.getRole(superRoleName);
}
protected void addHierarchicalRoleToParent(RemoteDatabase database, PermissionMode permissionMode, ORole... roles) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
User user = database.getUser(userName);
for(Role role : roles) {
user.addRole(role);
}
user.save();
if(getParentSecurityContext() != null) {
getParentSecurityContext().addHierarchicalRoleToParent(database, permissionMode, roles);
}
}
protected void createRolesAndUsers(RemoteDatabase database) {
boolean[] booleanArray;
if(hierarchical) {
booleanArray = new boolean[] {false, true};
} else {
booleanArray = new boolean[] {false};
}
for(boolean hierarchical : booleanArray) {
for(PermissionMode permissionMode : PermissionMode.values()) {
Role superRole = getSuperRole(database, permissionMode);
String roleName = getSecurityRoleOrUserName(permissionMode, SecurityType.ROLE, hierarchical);
Role role = database.createRole(roleName, superRole, ALLOW_MODES.DENY_ALL_BUT);
addExtraRules(role, permissionMode);
role.save();
logger.trace("{} created", role);
if(hierarchical && getParentSecurityContext() != null) {
getParentSecurityContext().addHierarchicalRoleToParent(database, permissionMode, role);
}
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, hierarchical);
User user = database.createUser(userName, DatabaseEnvironment.DEFAULT_PASSWORDS.get(permissionMode),
role);
user.save();
logger.trace("{} created", user);
}
}
}
public void create(RemoteDatabase database) {
createRolesAndUsers(database);
logger.trace("Security Context (roles and users) with UUID {} successfully created", context.toString());
}
private void drop(RemoteDatabase database, String name, SecurityType securityType) {
switch(securityType) {
case ROLE:
database.dropRole(name);
break;
case USER:
database.dropUser(name);
break;
default:
break;
}
logger.trace("{} successfully dropped", name);
}
public void delete() throws ResourceRegistryException {
RemoteDatabase remoteDatabase = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal();
RemoteDatabase database = null;
try {
database = getAdminDatabaseDocument();
delete(database);
database.commit();
} finally {
if(database!=null) {
database.close();
}
// if(current!=null) {
// current.activateOnCurrentThread();
// }
}
}
protected void removeChildrenHRolesFromParents(RemoteDatabase remoteDatabase) {
Set<SecurityContext> parents = getAllParents();
Set<SecurityContext> allChildren = getAllChildren();
removeChildrenHRolesFromParents(remoteDatabase, parents, allChildren);
}
protected void removeChildrenHRolesFromParents(RemoteDatabase remoteDatabase, Set<SecurityContext> parents, Set<SecurityContext> children) {
for(SecurityContext parent : parents) {
parent.removeChildrenHRolesFromMyHUsers(remoteDatabase, children);
}
}
protected void removeChildrenHRolesFromMyHUsers(RemoteDatabase database, Set<SecurityContext> children) {
for(PermissionMode permissionMode : PermissionMode.values()) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
User user = database.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(RemoteDatabase database, PermissionMode permissionMode, String roleName) {
String userName = getSecurityRoleOrUserName(permissionMode, SecurityType.USER, true);
User user = database.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(RemoteDatabase remoteDatabase) {
boolean[] booleanArray;
if(hierarchical) {
booleanArray = new boolean[] {false, true};
} else {
booleanArray = new boolean[] {false};
}
for(boolean hierarchic : booleanArray) {
if(hierarchic) {
removeChildrenHRolesFromParents(remoteDatabase);
}
for(PermissionMode permissionMode : PermissionMode.values()) {
for(SecurityType securityType : SecurityType.values()) {
String name = getSecurityRoleOrUserName(permissionMode, securityType, hierarchic);
drop(remoteDatabase, name, securityType);
}
}
}
}
public void delete(RemoteDatabase remoteDatabase) {
// OSecurity oSecurity = getOSecurity(orientGraph);
logger.trace("Going to remove Security Context (roles and users) with UUID {}", context.toString());
deleteRolesAndUsers(remoteDatabase);
logger.trace("Security Context (roles and users) with UUID {} successfully removed", context.toString());
}
public RemoteDatabase getRemoteDatabase(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);
// }
return null;
}
@Override
public String toString() {
return String.format("%s %s", Context.NAME, getUUID().toString());
}
}