package org.gcube.data.access.storagehub.services; import static org.gcube.data.access.storagehub.Roles.INFRASTRUCTURE_MANAGER_ROLE; import static org.gcube.data.access.storagehub.Roles.VREMANAGER_ROLE; import java.security.Principal; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.inject.Inject; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; 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.DELETE; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; 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 javax.ws.rs.core.Response; import org.apache.jackrabbit.api.JackrabbitSession; import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; import org.apache.jackrabbit.api.security.user.Authorizable; import org.apache.jackrabbit.api.security.user.Group; import org.apache.jackrabbit.api.security.user.Query; import org.apache.jackrabbit.api.security.user.QueryBuilder; import org.apache.jackrabbit.api.security.user.User; import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils; import org.gcube.common.authorization.control.annotations.AuthorizationControl; import org.gcube.common.authorization.library.provider.AuthorizationProvider; import org.gcube.common.gxrest.response.outbound.GXOutboundErrorResponse; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean.Type; import org.gcube.common.storagehub.model.Excludes; import org.gcube.common.storagehub.model.Paths; 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.StorageHubException; import org.gcube.common.storagehub.model.exceptions.UserNotAuthorizedException; import org.gcube.common.storagehub.model.items.Item; 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.exception.MyAuthException; import org.gcube.data.access.storagehub.handlers.CredentialHandler; import org.gcube.data.access.storagehub.handlers.GroupHandler; import org.gcube.data.access.storagehub.handlers.TrashHandler; import org.gcube.data.access.storagehub.handlers.items.Node2ItemConverter; import org.gcube.data.access.storagehub.handlers.items.builders.FolderCreationParameters; import org.gcube.data.access.storagehub.handlers.vres.VRE; import org.gcube.data.access.storagehub.handlers.vres.VREManager; import org.gcube.smartgears.annotations.ManagedBy; import org.gcube.smartgears.utils.InnerMethodName; import org.glassfish.jersey.media.multipart.FormDataParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webcohesion.enunciate.metadata.rs.RequestHeader; import com.webcohesion.enunciate.metadata.rs.RequestHeaders; @Path("groups") @ManagedBy(StorageHubAppllicationManager.class) @RequestHeaders({ @RequestHeader( name = "Authorization", description = "Bearer token, see https://dev.d4science.org/how-to-access-resources"), }) public class GroupManager { @Context ServletContext context; @Inject TrashHandler trashHandler; private static final Logger log = LoggerFactory.getLogger(GroupManager.class); RepositoryInitializer repository = StorageHubAppllicationManager.repository; @Inject VREManager vreManager; @Inject GroupHandler groupHandler; @Inject Node2ItemConverter node2Item; @Inject PathUtil pathUtil; @Inject AuthorizationChecker authChecker; @GET @Path("") @Produces(MediaType.APPLICATION_JSON) public List getGroups(){ InnerMethodName.instance.set("getGroups"); JackrabbitSession session = null; List groups= new ArrayList<>(); try { session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); Iterator result = session.getUserManager().findAuthorizables(new Query() { @Override public void build(QueryBuilder builder) { builder.setSelector(Group.class); } }); while (result.hasNext()) { Authorizable group = result.next(); log.info("group {} found",group.getPrincipal().getName()); groups.add(group.getPrincipal().getName()); } }catch(RepositoryException re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); } finally { if (session!=null) session.logout(); } return groups; } @POST @Path("") @Consumes(MediaType.MULTIPART_FORM_DATA) @AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}, exception=MyAuthException.class) public String createGroup(@FormDataParam("group") String group, @FormDataParam("accessType") AccessType accessType, @FormDataParam("folderOwner") String folderOwner){ InnerMethodName.instance.set("createGroup"); JackrabbitSession session = null; String groupId = null; try { log.info("create group called with groupid {} , accessType {} and folderOwner {}",group, accessType, folderOwner); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); org.apache.jackrabbit.api.security.user.UserManager usrManager = session.getUserManager(); Group createdGroup = usrManager.createGroup(group); groupId = createdGroup.getID(); User user = (User)usrManager.getAuthorizable(folderOwner); createVreFolder(groupId, session, accessType!=null?accessType:AccessType.WRITE_OWNER, folderOwner); boolean success = this.internalAddUserToGroup(session, createdGroup, user); if (!success) log.warn("the user have not been added to the group"); else log.debug("the user have been added to the group"); session.save(); }catch(StorageHubException se) { log.error("error creating group {}", group, se); GXOutboundErrorResponse.throwException(se); }catch(Throwable e) { log.error("jcr error creating group {}", group, e); GXOutboundErrorResponse.throwException(new BackendGenericError(e)); } finally { if (session!=null) session.logout(); } return groupId; } @DELETE @Path("{group}") @AuthorizationControl(allowedRoles={INFRASTRUCTURE_MANAGER_ROLE}, exception=MyAuthException.class) public String deleteGroup(@PathParam("group") String group){ InnerMethodName.instance.set("deleteGroup"); JackrabbitSession session = null; try { session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); org.apache.jackrabbit.api.security.user.UserManager usrManager = session.getUserManager(); Authorizable authorizable = usrManager.getAuthorizable(group); if (authorizable!=null && authorizable.isGroup()) authorizable.remove(); try { Node node = groupHandler.getFolderNodeRelatedToGroup(session, group); List workspaceItems = Utils.getItemList(node, Excludes.GET_ONLY_CONTENT, null, true, null); trashHandler.removeOnlyNodesContent(session, workspaceItems); node.removeSharedSet(); }catch (Exception e) { log.warn("vreFolder {} not found, removing only the group", group); } session.save(); }catch(RepositoryException re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); } finally { if (session!=null) session.logout(); } return group; } public boolean isInfraManager() { return AuthorizationProvider.instance.get().getClient().getRoles().contains(INFRASTRUCTURE_MANAGER_ROLE); } public boolean isVREManager() { return AuthorizationProvider.instance.get().getClient().getRoles().contains(VREMANAGER_ROLE); } @PUT @Path("{id}/admins") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void addAdmin(@PathParam("id") String groupId, @FormParam("userId") String userId){ InnerMethodName.instance.set("addAdmin"); JackrabbitSession session = null; try { Objects.nonNull(groupId); Objects.nonNull(userId); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); Node vreFolder = groupHandler.getFolderNodeRelatedToGroup(session, groupId); String currentUser = AuthorizationProvider.instance.get().getClient().getId(); if (!isInfraManager() && !(isVREManager() && isValidGroupForContext(groupId) )) authChecker.checkAdministratorControl(session, currentUser, node2Item.getItem(vreFolder, Excludes.ALL)); org.apache.jackrabbit.api.security.user.UserManager usrManager = ((JackrabbitSession)session).getUserManager(); Group group = (Group)usrManager.getAuthorizable(groupId); User authUser = (User)usrManager.getAuthorizable(userId); if (group ==null) throw new InvalidCallParameters("invalid group "+groupId); if (authUser ==null) throw new InvalidCallParameters("invalid user "+userId); if (!group.isMember(authUser)) throw new InvalidCallParameters("user "+userId+" is not in the group "+groupId); AccessControlManager acm = session.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, vreFolder.getPath()); Privilege[] userPrivileges = new Privilege[] { acm.privilegeFromName(AccessType.ADMINISTRATOR.getValue()) }; Principal principal = AccessControlUtils.getPrincipal(session, userId); acls.addAccessControlEntry(principal, userPrivileges); acm.setPolicy(vreFolder.getPath(), acls); session.save(); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(Throwable re ){ log.error("adding admin to VREFolder", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error adding admin to VREFolder", re)); } finally { if (session!=null) session.logout(); } } @DELETE @Path("{id}/admins/{userId}") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public void removeAdmin(@PathParam("id") String groupId, @PathParam("userId") String userId){ InnerMethodName.instance.set("removeAdmin"); JackrabbitSession session = null; try { Objects.nonNull(groupId); Objects.nonNull(userId); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); Node vreFolder = groupHandler.getFolderNodeRelatedToGroup(session, groupId); String currentUser = AuthorizationProvider.instance.get().getClient().getId(); if (!isInfraManager() && !(isVREManager() && isValidGroupForContext(groupId) )) authChecker.checkAdministratorControl(session, currentUser, node2Item.getItem(vreFolder, Excludes.ALL)); AccessControlManager acm = session.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, vreFolder.getPath()); AccessControlEntry toRemove = null; for (AccessControlEntry acl: acls.getAccessControlEntries()) if (acl.getPrincipal().getName().equals(userId)) { toRemove = acl; break; } acls.removeAccessControlEntry(toRemove); acm.setPolicy(vreFolder.getPath(), acls); session.save(); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(Throwable re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error removing admin to VREFolder", re)); }finally { if (session!=null) session.logout(); } } @GET @Path("{groupId}/admins") @Produces(MediaType.APPLICATION_JSON) public List getAdmins(@PathParam("groupId") String groupId){ InnerMethodName.instance.set("getAdmins"); String login = AuthorizationProvider.instance.get().getClient().getId(); JackrabbitSession session = null; List users = new ArrayList<>(); try { session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); VRE vreFolder = vreManager.getVreFolderItemByGroupName(session, groupId, login, Excludes.ALL); AccessControlManager acm = session.getAccessControlManager(); //authChecker.checkAdministratorControl(session, (VreFolder)vreFolder.getVreFolder()); Node node = session.getNodeByIdentifier(vreFolder.getVreFolder().getId()); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, node.getPath()); for (AccessControlEntry acl: acls.getAccessControlEntries()) for (Privilege pr: acl.getPrivileges()) { if (pr.getName().equals(AccessType.ADMINISTRATOR.getValue())){ users.add(acl.getPrincipal().getName()); } } }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(Exception re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); }finally { if (session!=null) session.logout(); } return users; } @PUT @Path("{id}/users") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @AuthorizationControl(allowedRoles={VREMANAGER_ROLE, INFRASTRUCTURE_MANAGER_ROLE}, exception=MyAuthException.class) public boolean addUserToGroup(@PathParam("id") String groupId, @FormParam("userId") String userId){ InnerMethodName.instance.set("addUserToGroup"); JackrabbitSession session = null; boolean success = false; try { if (!isInfraManager() && !isValidGroupForContext(groupId)) throw new UserNotAuthorizedException("only VREManager of the selected VRE can execute this operation"); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); org.apache.jackrabbit.api.security.user.UserManager usrManager = session.getUserManager(); Group group = (Group)usrManager.getAuthorizable(groupId); User user = (User)usrManager.getAuthorizable(userId); if (user==null) throw new InvalidCallParameters("user "+userId+" not exists"); if (group.isMember(user)) throw new InvalidCallParameters("user "+userId+" is already member of group "+groupId); this.internalAddUserToGroup(session, group, user); session.save(); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(RepositoryException re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); }finally { if (session!=null) session.logout(); } return success; } private boolean internalAddUserToGroup(JackrabbitSession session, Group group, User user) throws RepositoryException, StorageHubException { boolean success = group.addMember(user); session.save(); String folderName = group.getPrincipal().getName(); Node folder = groupHandler.getFolderNodeRelatedToGroup(session, folderName); String userPath = Paths.append(pathUtil.getVREsPath(user.getPrincipal().getName(), session), folderName).toPath(); log.debug("creating folder in user path {} from {}", userPath, folder.getPath() ); session.getWorkspace().clone(session.getWorkspace().getName(), folder.getPath(),userPath , false); try { session.getNode(userPath); log.debug("the new folder exists ({}) ", userPath ); }catch (PathNotFoundException e) { log.debug("the new folder doesn't exists ({}) ", userPath ); } return success; } @DELETE @Path("{groupId}/users/{userId}") @AuthorizationControl(allowedRoles={VREMANAGER_ROLE, INFRASTRUCTURE_MANAGER_ROLE}, exception=MyAuthException.class) public boolean removeUserFromGroup(@PathParam("groupId") String groupId, @PathParam("userId") String userId){ InnerMethodName.instance.set("removeUserFromGroup"); JackrabbitSession session = null; boolean success = false; try { if (!isValidGroupForContext(groupId) && !isInfraManager()) throw new UserNotAuthorizedException("only VREManager of the selected VRE can execute this operation"); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); success = groupHandler.removeUserFromGroup(groupId, userId, session); session.save(); }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(RepositoryException re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); } finally { if (session!=null) session.logout(); } return success; } @GET @Path("{groupId}/users") @Produces(MediaType.APPLICATION_JSON) @AuthorizationControl(allowedRoles={VREMANAGER_ROLE, INFRASTRUCTURE_MANAGER_ROLE}, exception=MyAuthException.class) public List getUsersOfGroup(@PathParam("groupId") String groupId){ InnerMethodName.instance.set("getUsersOfGroup"); JackrabbitSession session = null; List users = new ArrayList<>(); try { if (!isValidGroupForContext(groupId) && !isInfraManager()) throw new UserNotAuthorizedException("only VREManager of the selected VRE can execute this operation"); session = (JackrabbitSession) repository.getRepository().login(CredentialHandler.getAdminCredentials(context)); org.apache.jackrabbit.api.security.user.UserManager usrManager = session.getUserManager(); Group group = (Group)usrManager.getAuthorizable(groupId); Iterator it = group.getMembers(); while (it.hasNext()) { Authorizable user = it.next(); users.add(user.getPrincipal().getName()); } }catch(StorageHubException she ){ log.error(she.getErrorMessage(), she); GXOutboundErrorResponse.throwException(she, Response.Status.fromStatusCode(she.getStatus())); }catch(RepositoryException re ){ log.error("jcr error creating item", re); GXOutboundErrorResponse.throwException(new BackendGenericError("jcr error creating item", re)); }finally { if (session!=null) session.logout(); } return users; } private void createVreFolder(String groupId, JackrabbitSession session, AccessType defaultAccessType, String owner ) throws Exception{ Node sharedRootNode = session.getNode(Constants.SHARED_FOLDER_PATH); String name = groupId; String currentScope = ScopeProvider.instance.get(); ScopeBean bean = new ScopeBean(currentScope); while (!bean.is(Type.INFRASTRUCTURE)) { bean = bean.enclosingScope(); } String root = bean.toString().replaceAll("/", ""); String displayName = groupId.replaceAll(root+"-[^\\-]*\\-(.*)", "$1"); log.info("creating vreFolder with name {} and title {} and owner {}", name, displayName, owner); FolderCreationParameters folderParameters = FolderCreationParameters.builder().name(name).description( "VREFolder for "+groupId).author(owner).on(sharedRootNode.getIdentifier()).with(session).build(); Node folder= Utils.createFolderInternally(folderParameters, null); folder.setPrimaryType(PrimaryNodeType.NT_WORKSPACE_SHARED_FOLDER); folder.setProperty(NodeProperty.IS_VRE_FOLDER.toString(), true); folder.setProperty(NodeProperty.TITLE.toString(), name); folder.setProperty(NodeProperty.DISPLAY_NAME.toString(), displayName); session.save(); AccessControlManager acm = session.getAccessControlManager(); JackrabbitAccessControlList acls = AccessControlUtils.getAccessControlList(acm, folder.getPath()); /*Privilege[] adminPrivileges = new Privilege[] { acm.privilegeFromName(AccessType.ADMINISTRATOR.getValue()) }; acls.addAccessControlEntry(AccessControlUtils.getPrincipal(session, AuthorizationProvider.instance.get().getClient().getId()), adminPrivileges ); */ Privilege[] usersPrivileges = new Privilege[] { acm.privilegeFromName(defaultAccessType.getValue()) }; acls.addAccessControlEntry(AccessControlUtils.getPrincipal(session,groupId), usersPrivileges ); acm.setPolicy(folder.getPath(), acls); log.debug("vrefolder created with id {}",folder.getIdentifier()); } private boolean isValidGroupForContext(String group){ String currentContext = ScopeProvider.instance.get(); String expectedGroupId= currentContext.replace("/", "-").substring(1); return group.equals(expectedGroupId); } }