/** * */ 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> databasesMap; protected SecurityContext parentSecurityContext; protected Set 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 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 getAllChildren(){ Set allChildren = new HashSet<>(); allChildren.add(this); for(SecurityContext securityContext : getChildren()) { allChildren.addAll(securityContext.getAllChildren()); } return allChildren; } /** * @return */ private Set getAllParents(){ Set 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 allChildren = getAllChildren(); Set oldParents = getAllParents(); Set 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 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 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 getContexts(Document element) { Set 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 callable = new Callable() { @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 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 parents = getAllParents(); Set allChildren = getAllChildren(); removeChildrenHRolesFromParents(remoteDatabase, parents, allChildren); } protected void removeChildrenHRolesFromParents(RemoteDatabase remoteDatabase, Set parents, Set children) { for(SecurityContext parent : parents) { parent.removeChildrenHRolesFromMyHUsers(remoteDatabase, children); } } protected void removeChildrenHRolesFromMyHUsers(RemoteDatabase database, Set 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()); } }