package org.gcube.data.access.storagehub.services; import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.security.AccessControlEntry; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; import javax.servlet.ServletContext; import javax.ws.rs.Consumes; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; import org.gcube.common.authorization.library.provider.AuthorizationProvider; import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse; import org.gcube.common.storagehub.model.Excludes; import org.gcube.common.storagehub.model.NodeConstants; import org.gcube.common.storagehub.model.acls.AccessType; import org.gcube.common.storagehub.model.exceptions.BackendGenericError; import org.gcube.common.storagehub.model.exceptions.InvalidCallParameters; import org.gcube.common.storagehub.model.exceptions.InvalidItemException; import org.gcube.common.storagehub.model.exceptions.StorageHubException; import org.gcube.common.storagehub.model.exceptions.UserNotAuthorizedException; import org.gcube.common.storagehub.model.items.FolderItem; import org.gcube.common.storagehub.model.items.Item; import org.gcube.common.storagehub.model.items.SharedFolder; import org.gcube.common.storagehub.model.types.ItemAction; import org.gcube.common.storagehub.model.types.NodeProperty; import org.gcube.common.storagehub.model.types.PrimaryNodeType; import org.gcube.data.access.storagehub.AuthorizationChecker; import org.gcube.data.access.storagehub.Constants; import org.gcube.data.access.storagehub.Utils; import org.gcube.data.access.storagehub.accounting.AccountingHandler; import org.gcube.data.access.storagehub.handlers.CredentialHandler; import org.gcube.data.access.storagehub.handlers.Item2NodeConverter; import org.gcube.data.access.storagehub.handlers.Node2ItemConverter; import org.gcube.data.access.storagehub.handlers.VersionHandler; import org.gcube.smartgears.utils.InnerMethodName; import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Path("items") public class ItemSharing { private static final Logger log = LoggerFactory.getLogger(ItemSharing.class); @Inject RepositoryInitializer repository; @Inject AccountingHandler accountingHandler; @RequestScoped @PathParam("id") String id; @Context ServletContext context; @Inject AuthorizationChecker authChecker; @Inject VersionHandler versionHandler; @Inject Node2ItemConverter node2Item; @Inject Item2NodeConverter item2Node; @PUT @Path("{id}/share") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public String share(@FormDataParam("users") Set users, @FormDataParam("defaultAccessType") AccessType accessType){ InnerMethodName.instance.set("shareFolder"); Session ses = null; String toReturn = null; try{ String login = AuthorizationProvider.instance.get().getClient().getId(); ses = repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); authChecker.checkWriteAuthorizationControl(ses, id, false); //Item item = node2Item.getItem(ses.getNodeByIdentifier(id), Excludes.ALL); if (accessType==null) accessType = AccessType.READ_ONLY; if (users==null || users.isEmpty()) throw new InvalidCallParameters("users is empty"); Node nodeToShare = ses.getNodeByIdentifier(id); boolean alreadyShared = false; Node sharedFolderNode; if (!node2Item.checkNodeType(nodeToShare, SharedFolder.class)) sharedFolderNode= shareFolder(nodeToShare, ses); else { sharedFolderNode = nodeToShare; alreadyShared = true; } ses.save(); ses.getWorkspace().getLockManager().lock(sharedFolderNode.getPath(), true, true, 0,login); try { AccessControlManager acm = ses.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, sharedFolderNode.getPath()); if (!alreadyShared) { Privilege[] adminPrivileges = new Privilege[] { acm.privilegeFromName(AccessType.ADMINISTRATOR.getValue()) }; addUserToSharing(sharedFolderNode, ses, login, adminPrivileges, acls); users.remove(login); } Privilege[] userPrivileges = new Privilege[] { acm.privilegeFromName(accessType.getValue()) }; for (String user : users) try { addUserToSharing(sharedFolderNode, ses, user, userPrivileges, acls); }catch(Exception e){ log.warn("error adding user {} to sharing of folder {}", user, sharedFolderNode.getName()); } acm.setPolicy(sharedFolderNode.getPath(), acls); accountingHandler.createShareFolder(sharedFolderNode.getProperty(NodeProperty.TITLE.toString()).getString(), users, ses, sharedFolderNode, false); ses.save(); toReturn = sharedFolderNode.getIdentifier(); } finally { ses.getWorkspace().getLockManager().unlock(sharedFolderNode.getPath()); } }catch(RepositoryException re){ log.error("jcr sharing", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error extracting archive", re)); }catch(StorageHubException she ){ log.error("error sharing", she); GXOutboundErrorResponse.throwException(she); }finally{ if (ses!=null) ses.logout(); } return toReturn; } private Node shareFolder(Node node, Session ses) throws RepositoryException, BackendGenericError, StorageHubException{ String login = AuthorizationProvider.instance.get().getClient().getId(); if (!node2Item.checkNodeType(node, FolderItem.class) || Utils.hasSharedChildren(node) || !node.getProperty(NodeProperty.OWNER.toString()).getString().equals(login)) throw new InvalidItemException("item with id "+id+" cannot be shared"); String sharedFolderName = node.getIdentifier(); String newNodePath = Constants.SHARED_FOLDER_PATH+"/"+sharedFolderName; ses.move(node.getPath(),newNodePath); Node sharedFolderNode = ses.getNode(newNodePath); sharedFolderNode.setPrimaryType(PrimaryNodeType.NT_WORKSPACE_SHARED_FOLDER); return sharedFolderNode; } private void addUserToSharing(Node sharedFolderNode, Session ses, String user, Privilege[] userPrivileges, JackrabbitAccessControlList acls) throws RepositoryException{ ses.getWorkspace().clone(ses.getWorkspace().getName(), sharedFolderNode.getPath(), sharedFolderNode.getProperty(NodeProperty.TITLE.toString()).getString(), false); String userRootWSId = ses.getNode(Utils.getHomePath(user).toPath()).getIdentifier(); acls.addAccessControlEntry(AccessControlUtils.getPrincipal(ses, user), userPrivileges ); Node usersNode =null; if (sharedFolderNode.hasNode(NodeConstants.USERS_NAME)) usersNode = sharedFolderNode.getNode(NodeConstants.USERS_NAME); else usersNode = sharedFolderNode.addNode(NodeConstants.USERS_NAME); usersNode.setProperty(user, String.format("%s/%s",userRootWSId,sharedFolderNode.getProperty(NodeProperty.TITLE.toString()).getString())); } @PUT @Path("{id}/unshare") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.MULTIPART_FORM_DATA) public String unshare(@FormDataParam("users") Set users){ InnerMethodName.instance.set("unshareFolder"); String login = AuthorizationProvider.instance.get().getClient().getId(); Session ses = null; String toReturn = null; try { ses = repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); Node sharedNode = ses.getNodeByIdentifier(id); Item item = node2Item.getItem(sharedNode, Excludes.ALL); if (!(item instanceof FolderItem) || !((FolderItem) item).isShared() || ((SharedFolder) item).isVreFolder()) throw new InvalidItemException("item with id "+id+" cannot be unshared"); SharedFolder sharedItem =(SharedFolder) item; Set usersInSharedFolder = new HashSet<>(sharedItem.getUsers().getValues().keySet()); usersInSharedFolder.removeAll(users); if (users==null || users.size()==0 || usersInSharedFolder.size()<=1) return unshareAll(login, ses, sharedItem); ses.getWorkspace().getLockManager().lock(sharedNode.getPath(), true, true, 0,login); try { if (users.size()==1 && users.contains(login)) toReturn = unshareCaller(login, ses, sharedItem); else toReturn = unsharePartial(users, login, ses, sharedItem); }finally { ses.getWorkspace().getLockManager().unlock(sharedNode.getPath()); } }catch(RepositoryException re){ log.error("jcr unsharing", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error extracting archive", re)); }catch(StorageHubException she ){ log.error("error unsharing", she); GXOutboundErrorResponse.throwException(she); }finally{ if (ses!=null) ses.logout(); } return toReturn; } private String unshareAll(String login, Session ses, SharedFolder item) throws StorageHubException, BackendGenericError, RepositoryException{ authChecker.checkAdministratorControl(ses, item); if (!login.equals(item.getOwner())) throw new UserNotAuthorizedException("user "+login+" not authorized to unshare all"); Node sharedItemNode = ses.getNodeByIdentifier(item.getId()); ses.getWorkspace().getLockManager().lock(sharedItemNode.getPath(), true, true, 0,login); Node unsharedNode; try { log.debug("user list is empty, I'm going to remove also the shared dir"); //TODO: take the admin folder and remove his clone then move the shared folder from share to the user home and change the folder type String adminDirPath = (String)item.getUsers().getValues().get(login); String[] splitString = adminDirPath.split("/"); String parentDirectoryId = splitString[0]; String directoryName = splitString[1]; Node parentNode = ses.getNodeByIdentifier(parentDirectoryId); log.debug("parent node path is {}/{}",parentNode.getPath(), directoryName); Node adminNode = ses.getNode(String.format("%s/%s",parentNode.getPath(), directoryName)); adminNode.removeShare(); unsharedNode = createUnsharedFolder(ses, parentNode, directoryName, item.getDescription(), login); List itemsToCopy = Utils.getItemList(sharedItemNode, Excludes.ALL, null, true, null); for (Item itemCopy: itemsToCopy) { Node itemToCopyNode = ses.getNodeByIdentifier(itemCopy.getId()); log.debug("copying {} to {}", itemToCopyNode.getPath(), unsharedNode.getPath()); ses.move(itemToCopyNode.getPath(), String.format("%s/%s",unsharedNode.getPath(), itemToCopyNode.getName())); } ses.save(); }finally { ses.getWorkspace().getLockManager().unlock(sharedItemNode.getPath()); } sharedItemNode.removeSharedSet(); ses.save(); log.debug("all the users have been removed, the folder is totally unshared"); return unsharedNode.getIdentifier(); } private String unshareCaller(String login, Session ses, SharedFolder item) throws StorageHubException, RepositoryException{ if (login.equals(item.getOwner())) throw new InvalidCallParameters("the callor is the owner, the folder cannot be unshared"); if (item.getUsers().getValues().get(login)==null) throw new InvalidCallParameters("the folder is not shared with user "+login); Node sharedFolderNode =ses.getNodeByIdentifier(item.getId()); String parentId = removeSharingForUser(login, ses, item); AccessControlManager acm = ses.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, sharedFolderNode.getPath()); AccessControlEntry entryToDelete= null; for (AccessControlEntry ace :acls.getAccessControlEntries()) { if (ace.getPrincipal().getName().equals(login)) { entryToDelete = ace; break; } } if (entryToDelete!=null) acls.removeAccessControlEntry(entryToDelete); log.debug("removed Access control entry for user {}",login); Node sharedItemNode = ses.getNodeByIdentifier(item.getId()); Node usersNode = sharedItemNode.getNode(NodeConstants.USERS_NAME); usersNode.remove(); Node newUsersNode = sharedItemNode.addNode(NodeConstants.USERS_NAME); item.getUsers().getValues().entrySet().stream().filter(entry -> !entry.getKey().equals(login)).forEach(entry-> {try { newUsersNode.setProperty(entry.getKey(), (String)entry.getValue()); } catch (Exception e) { log.error("error adding property to shared node users node under "+item.getId()); }}); acm.setPolicy(sharedFolderNode.getPath(), acls); ses.save(); return parentId; } private String unsharePartial(Set usersToUnshare, String login, Session ses, SharedFolder item) throws StorageHubException, RepositoryException { authChecker.checkAdministratorControl(ses, (SharedFolder)item); if (usersToUnshare.contains(item.getOwner())) throw new UserNotAuthorizedException("user "+login+" not authorized to unshare owner"); Node sharedFolderNode =ses.getNodeByIdentifier(item.getId()); AccessControlManager acm = ses.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, sharedFolderNode.getPath()); for (String user : usersToUnshare) { removeSharingForUser(user, ses, item); AccessControlEntry entryToDelete= null; for (AccessControlEntry ace :acls.getAccessControlEntries()) { if (ace.getPrincipal().getName().equals(login)) { entryToDelete = ace; break; } } if (entryToDelete!=null) acls.removeAccessControlEntry(entryToDelete); } log.debug("removed Access control entry for user {}",login); Node sharedItemNode = ses.getNodeByIdentifier(item.getId()); Node usersNode = sharedItemNode.getNode(NodeConstants.USERS_NAME); usersNode.remove(); Node newUsersNode = sharedItemNode.addNode(NodeConstants.USERS_NAME); item.getUsers().getValues().entrySet().stream().filter(entry -> !usersToUnshare.contains(entry.getKey())).forEach(entry-> {try { newUsersNode.setProperty(entry.getKey(), (String)entry.getValue()); } catch (Exception e) { log.error("error adding property to shared node users node under "+item.getId()); }}); acm.setPolicy(sharedFolderNode.getPath(), acls); ses.save(); return item.getId(); } public String removeSharingForUser(String user, Session ses, SharedFolder item) throws RepositoryException { String userDirPath = (String)item.getUsers().getValues().get(user); if (userDirPath==null) return null; String[] splitString = userDirPath.split("/"); String parentDirectoryId = splitString[0]; String directoryName = splitString[1]; Node parentNode = ses.getNodeByIdentifier(parentDirectoryId); Node userNode = ses.getNode(String.format("%s/%s",parentNode.getPath(), directoryName)); userNode.removeShare(); accountingHandler.createUnshareFolder(directoryName, ses, parentNode, false); log.debug("directory removed for user {}",user); return parentDirectoryId; } private Node createUnsharedFolder(Session ses, Node destinationNode, String name, String description, String login) { FolderItem item = new FolderItem(); Calendar now = Calendar.getInstance(); item.setName(name); item.setTitle(name); item.setDescription(description); //item.setCreationTime(now); item.setHidden(false); item.setLastAction(ItemAction.CREATED); item.setLastModificationTime(now); item.setLastModifiedBy(login); item.setOwner(login); //to inherit hidden property //item.setHidden(destinationItem.isHidden()); Node newNode = item2Node.getNode(ses, destinationNode, item); return newNode; } }