package org.gcube.data.access.storagehub.services; import java.util.HashMap; import java.util.Map.Entry; 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.lock.LockException; import javax.jcr.security.AccessControlManager; import javax.jcr.security.Privilege; import javax.servlet.ServletContext; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; 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.ItemLockedException; import org.gcube.common.storagehub.model.exceptions.StorageHubException; import org.gcube.common.storagehub.model.items.ExternalFolder; 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.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.PathUtil; import org.gcube.data.access.storagehub.StorageHubAppllicationManager; 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.UnshareHandler; import org.gcube.data.access.storagehub.handlers.items.Item2NodeConverter; import org.gcube.data.access.storagehub.handlers.items.Node2ItemConverter; 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 extends Impersonable{ private static final Logger log = LoggerFactory.getLogger(ItemSharing.class); RepositoryInitializer repository = StorageHubAppllicationManager.repository; @Inject AccountingHandler accountingHandler; @RequestScoped @PathParam("id") String id; @Context ServletContext context; @Inject AuthorizationChecker authChecker; @Inject PathUtil pathUtil; @Inject UnshareHandler unshareHandler; @Inject Node2ItemConverter node2Item; @Inject Item2NodeConverter item2Node; @SuppressWarnings("unchecked") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @POST @Path("{id}/share") public String shareWithMap(@FormParam("mapUserPermission") String mapUserPermissionString, @FormParam("defaultAccessType") String defaultAccessTypeString){ InnerMethodName.instance.set("shareFolder"); HashMap mapUserPermission; Session ses = null; String toReturn = null; try{ try { mapUserPermission = (HashMap)new ObjectMapper().readValue(mapUserPermissionString, HashMap.class); }catch (Exception e) { throw new InvalidCallParameters("invalid map passed"); } AccessType defaultAccessType; try { defaultAccessType = AccessType.fromValue(defaultAccessTypeString); }catch (IllegalArgumentException e) { throw new InvalidCallParameters("invalid default accessType"); } ses = repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); authChecker.checkWriteAuthorizationControl(ses, currentUser, id, false); Item item = node2Item.getItem(ses.getNodeByIdentifier(id), Excludes.ALL); if (mapUserPermission==null || mapUserPermission.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(); try { ses.getWorkspace().getLockManager().lock(sharedFolderNode.getPath(), true, true, 0,currentUser); }catch (LockException e) { throw new ItemLockedException(e); } 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, currentUser, item, adminPrivileges, acls); mapUserPermission.remove(currentUser); } for (Entry entry: mapUserPermission.entrySet() ) try { AccessType accessType = defaultAccessType; try { if (entry.getValue()!=null) accessType = AccessType.fromValue(entry.getValue()); }catch (IllegalArgumentException e) { log.warn("an illegal accessType passed to sharing ({})", entry.getValue());} Privilege[] userPrivileges = new Privilege[] { acm.privilegeFromName(accessType.getValue()) }; addUserToSharing(sharedFolderNode, ses, entry.getKey(), null, userPrivileges, acls); }catch(Exception e){ log.warn("error adding user {} to sharing of folder {}", entry.getKey(), sharedFolderNode.getName()); } acm.setPolicy(sharedFolderNode.getPath(), acls); String title = sharedFolderNode.getProperty(NodeProperty.TITLE.toString()).getString(); accountingHandler.createShareFolder(title, mapUserPermission.keySet(), ses, sharedFolderNode, currentUser, false); ses.save(); toReturn = sharedFolderNode.getIdentifier(); } finally { if (!ses.hasPendingChanges()) ses.getWorkspace().getLockManager().unlock(sharedFolderNode.getPath()); } }catch(RepositoryException re){ log.error("jcr sharing", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error sharing folder", re)); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }finally{ if (ses!=null) ses.logout(); } return toReturn; } @PUT @Path("{id}/share") @Consumes(MediaType.MULTIPART_FORM_DATA) @Deprecated public String share(@FormDataParam("users") Set users, @FormDataParam("defaultAccessType") AccessType accessType){ InnerMethodName.instance.set("shareFolder"); Session ses = null; String toReturn = null; try{ ses = repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); authChecker.checkWriteAuthorizationControl(ses, currentUser, 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(); try { ses.getWorkspace().getLockManager().lock(sharedFolderNode.getPath(), true, true, 0,currentUser); }catch (LockException e) { throw new ItemLockedException(e); } 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, currentUser, item, adminPrivileges, acls); users.remove(currentUser); } Privilege[] userPrivileges = new Privilege[] { acm.privilegeFromName(accessType.getValue()) }; for (String user : users) try { addUserToSharing(sharedFolderNode, ses, user, null, 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, currentUser, false); ses.save(); toReturn = sharedFolderNode.getIdentifier(); } finally { if (!ses.hasPendingChanges()) ses.getWorkspace().getLockManager().unlock(sharedFolderNode.getPath()); } }catch(RepositoryException re){ log.error("jcr sharing", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error sharing folder", re)); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }finally{ if (ses!=null) ses.logout(); } return toReturn; } private Node shareFolder(Node node, Session ses) throws RepositoryException, BackendGenericError, StorageHubException{ if (!node2Item.checkNodeType(node, FolderItem.class) || Utils.hasSharedChildren(node) || !node.getProperty(NodeProperty.PORTAL_LOGIN.toString()).getString().equals(currentUser)) 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, Item itemToShare, Privilege[] userPrivileges, JackrabbitAccessControlList acls) throws RepositoryException{ String userRootWSId; String userPath; if (itemToShare==null) { String userRootWS = pathUtil.getWorkspacePath(user).toPath(); userRootWSId = ses.getNode(userRootWS).getIdentifier(); userPath = String.format("%s%s",userRootWS,sharedFolderNode.getProperty(NodeProperty.TITLE.toString()).getString()); } else { userPath = itemToShare.getPath(); userRootWSId = itemToShare.getParentId(); } log.info("cloning directory to {} ",userPath); ses.getWorkspace().clone(ses.getWorkspace().getName(), sharedFolderNode.getPath(), userPath , false); 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") @Consumes(MediaType.MULTIPART_FORM_DATA) public String unshare(@FormDataParam("users") Set users){ InnerMethodName.instance.set("unshareFolder"); Session ses = null; String toReturn = null; try { log.debug("unsharing folder with id {} with users {}", id, users); ses = repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); Node sharedNode = ses.getNodeByIdentifier(id); toReturn = unshareHandler.unshare(ses, users, sharedNode, currentUser); if(toReturn == null ) throw new InvalidItemException("item with id "+id+" cannot be unshared"); }catch(RepositoryException re){ log.error("jcr unsharing", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error extracting archive", re)); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }finally{ if (ses!=null) ses.logout(); } return toReturn; } }