/* * Copyright (C) 2015 University of Dundee & Open Microscopy Environment. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package integration.chmod; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import ome.model.internal.Permissions; import ome.util.Utils; import omero.RLong; import omero.RType; import omero.ServerError; import omero.cmd.Chmod2; import omero.cmd.Delete2; import omero.gateway.util.Requests; import omero.model.Annotation; import omero.model.CommentAnnotationI; import omero.model.Dataset; import omero.model.DatasetImageLink; import omero.model.DatasetImageLinkI; import omero.model.Experiment; import omero.model.Experimenter; import omero.model.ExperimenterGroup; import omero.model.ExperimenterGroupI; import omero.model.ExperimenterI; import omero.model.IObject; import omero.model.Image; import omero.model.ImageAnnotationLink; import omero.model.ImageAnnotationLinkI; import omero.model.Instrument; import omero.model.Pixels; import omero.model.Plate; import omero.model.RectangleI; import omero.model.Roi; import omero.model.RoiI; import omero.model.TagAnnotation; import omero.model.TagAnnotationI; import omero.model.Thumbnail; import omero.sys.EventContext; import omero.sys.Parameters; import omero.sys.ParametersI; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; import integration.AbstractServerTest; /** * Tests that only appropriate users may use {@link Chmod2} and that others' data is then deleted only when appropriate. * @author m.t.b.carroll@dundee.ac.uk * @since 5.1.2 */ public class PermissionsTest extends AbstractServerTest { private final List<Long> testImages = Collections.synchronizedList(new ArrayList<Long>()); private ExperimenterGroup systemGroup; private EventContext userOtherGroup, adminOtherGroup; /** * Set up admin and non-admin users who are not a member of the groups created by tests. * @throws Exception unexpected */ @BeforeClass public void setupOtherGroup() throws Exception { systemGroup = new ExperimenterGroupI(iAdmin.getSecurityRoles().systemGroupId, false); userOtherGroup = newUserAndGroup("rw----"); final ExperimenterGroup group = new ExperimenterGroupI(userOtherGroup.groupId, false); adminOtherGroup = newUserInGroup(group, false); addUsers(systemGroup, Collections.singletonList(adminOtherGroup.userId), false); } /** * Clear the list of test images. */ @BeforeClass public void clearTestImages() { testImages.clear(); } /** * Delete the test images then clear the list. * @throws Exception unexpected */ @AfterClass public void deleteTestImages() throws Exception { final Delete2 delete = Requests.delete("Image", testImages); doChange(root, root.getSession(), delete, true); clearTestImages(); } /** * Add a comment and a ROI to the given image. * @param imageId an image ID * @return the new model objects * @throws ServerError unexpected */ private List<IObject> annotateImage(long imageId) throws ServerError { final ParametersI params = new ParametersI().addId(imageId); final Image image = (Image) iQuery.findByQuery("FROM Image i JOIN FETCH i.pixels WHERE i.id = :id", params); final List<IObject> annotationObjects = new ArrayList<IObject>(); for (final Annotation annotation : new Annotation[] {new CommentAnnotationI(), new TagAnnotationI()}) { ImageAnnotationLink link = new ImageAnnotationLinkI(); link.setParent((Image) image.proxy()); link.setChild(annotation); link = (ImageAnnotationLink) iUpdate.saveAndReturnObject(link); annotationObjects.add(link.proxy()); annotationObjects.add(link.getChild().proxy()); } Roi roi = new RoiI(); roi.addShape(new RectangleI()); roi.setImage((Image) image.proxy()); roi = (Roi) iUpdate.saveAndReturnObject(roi); annotationObjects.add(roi.proxy()); annotationObjects.add(roi.getShape(0).proxy()); Thumbnail thumbnail = mmFactory.createThumbnail(); thumbnail.setPixels((Pixels) image.getPrimaryPixels().proxy()); thumbnail = (Thumbnail) iUpdate.saveAndReturnObject(thumbnail); annotationObjects.add(thumbnail.proxy()); return annotationObjects; } /** * Test that a specific case of using {@link Chmod2} on an annotated image behaves as expected. * @param isGroupOwner if the user submitting the {@link Chmod2} request is an owner of the target group * @param isGroupMember if the user submitting the {@link Chmod2} request is a member of the target group * @param isDataOwner if the user submitting the {@link Chmod2} request owns the data in the group * @param isAdmin if the user submitting the {@link Chmod2} request is a member of the system group * @param fromPerms the permissions on the group before the chmod * @param toPerms the permissions on the group after the chmod * @param isExpectSuccess if the chmod is expected to succeed * @param isExpectDeleteOther if the chmod is expected to cause another user's annotations to be removed from a user's image * @throws Exception unexpected */ @Test(dataProvider = "chmod annotation test cases") public void testChmodAnnotation(boolean isGroupOwner, boolean isGroupMember, boolean isDataOwner, boolean isAdmin, String fromPerms, String toPerms, boolean isExpectSuccess, boolean isExpectDeleteOther) throws Exception { /* set up the users and group for this test case */ final EventContext importer, annotator, chmodder; final ExperimenterGroup dataGroup; importer = newUserAndGroup(fromPerms, isGroupOwner && isDataOwner); final long dataGroupId = importer.groupId; dataGroup = new ExperimenterGroupI(dataGroupId, false); if (isDataOwner) { chmodder = importer; } else if (isGroupMember) { chmodder = newUserInGroup(dataGroup, isGroupOwner); } else { chmodder = isAdmin ? adminOtherGroup : userOtherGroup; } if (isAdmin && chmodder != adminOtherGroup) { addUsers(systemGroup, Collections.singletonList(chmodder.userId), false); } if ("rwra--".equals(fromPerms)) { if (isGroupMember && !isDataOwner) { annotator = chmodder; } else { annotator = newUserInGroup(dataGroup, false); } } else { annotator = null; } /* note which objects were used to annotate an image */ final List<IObject> ownerAnnotations, otherAnnotations; /* import and annotate an image */ init(importer); final Image image = (Image) iUpdate.saveAndReturnObject(mmFactory.createImage()).proxy(); final long imageId = image.getId().getValue(); testImages.add(imageId); ownerAnnotations = annotateImage(imageId); disconnect(); /* perhaps have another user annotate the image */ if (annotator == null) { otherAnnotations = Collections.<IObject>emptyList(); } else { init(annotator); otherAnnotations = annotateImage(image.getId().getValue()); disconnect(); } /* perform the chmod */ init(chmodder); final Chmod2 chmod = Requests.chmod("ExperimenterGroup", dataGroupId, toPerms); doChange(client, factory, chmod, isExpectSuccess); disconnect(); if (!isExpectSuccess) { return; } /* check that the group's permissions are as requested */ logRootIntoGroup(dataGroupId); final ExperimenterGroup changedGroup = (ExperimenterGroup) iQuery.get("ExperimenterGroup", dataGroupId); final long actualPermissions = changedGroup.getDetails().getPermissions().getPerm1(); final long expectedPermissions = (Long) Utils.internalForm(Permissions.parseString(toPerms)); Assert.assertEquals(actualPermissions, expectedPermissions); /* pluck the tag from the other user's annotations */ final TagAnnotation tag; if (annotator == null) { tag = null; } else { final Iterator<IObject> annotationIterator = otherAnnotations.iterator(); while (true) { final IObject annotation = annotationIterator.next(); if (annotation instanceof TagAnnotation) { annotationIterator.remove(); tag = (TagAnnotation) annotation; break; } } } /* check that exactly the expected object deletions have occurred */ assertExists(image); if (tag != null) { assertExists(tag); } assertAllExist(ownerAnnotations); if (isExpectDeleteOther) { assertNoneExist(otherAnnotations); } else { assertAllExist(otherAnnotations); } disconnect(); } /** * @return a variety of test cases for annotation chmod */ @DataProvider(name = "chmod annotation test cases") public Object[][] provideChmodAnnotationCases() { int index = 0; final int IS_GROUP_OWNER = index++; final int IS_GROUP_MEMBER = index++; final int IS_DATA_OWNER = index++; final int IS_ADMIN = index++; final int FROM_PERMS = index++; final int TO_PERMS = index++; final int IS_EXPECT_SUCCESS = index++; final int IS_EXPECT_DELETE_OTHER = index++; final boolean[] booleanCases = new boolean[]{false, true}; final String[] permsCases = new String[]{"rw----", "rwra--"}; final List<Object[]> testCases = new ArrayList<Object[]>(); for (final boolean isGroupOwner : booleanCases) { for (final boolean isGroupMember : booleanCases) { for (final boolean isDataOwner : booleanCases) { for (final boolean isAdmin : booleanCases) { if (!isGroupMember && (isGroupOwner || isDataOwner)) { /* test case does not make sense */ continue; } for (final String fromPerms : permsCases) { for (final String toPerms : permsCases) { if (fromPerms.equals(toPerms)) { /* not a permissions change */ continue; } final Object[] testCase = new Object[index]; testCase[IS_GROUP_OWNER] = isGroupOwner; testCase[IS_GROUP_MEMBER] = isGroupMember; testCase[IS_DATA_OWNER] = isDataOwner; testCase[IS_ADMIN] = isAdmin; testCase[FROM_PERMS] = fromPerms; testCase[TO_PERMS] = toPerms; testCase[IS_EXPECT_SUCCESS] = isAdmin || isGroupOwner; testCase[IS_EXPECT_DELETE_OTHER] = "rwra--".equals(fromPerms) && "rw----".equals(toPerms); // DEBUG: if (isGroupOwner == true && isGroupMember == true && isDataOwner == true && // isAdmin == true && "rwra--".equals(fromPerms) && "rw----".equals(toPerms)) testCases.add(testCase); } } } } } } return testCases.toArray(new Object[testCases.size()][]); } /** * Test a specific case of using {@link Chmod2} on an image that is in a dataset. * @param isImageOwner if the user who owns the dataset also owns the image * @param isLinkOwner if the user who owns the dataset also linked the image to the dataset * @throws Exception unexpected */ @Test(dataProvider = "chmod container test cases") public void testChmodContainerReadWriteToPrivate(boolean isImageOwner, boolean isLinkOwner) throws Exception { /* set up the users and group for this test case */ final EventContext datasetOwner, imageOwner, linkOwner, chmodder; final ExperimenterGroup dataGroup; datasetOwner = newUserAndGroup("rwrw--"); final long dataGroupId = datasetOwner.groupId; dataGroup = new ExperimenterGroupI(dataGroupId, false); if (isImageOwner) { imageOwner = datasetOwner; linkOwner = isLinkOwner ? datasetOwner : newUserInGroup(dataGroup, false); } else { imageOwner = newUserInGroup(dataGroup, false); linkOwner = isLinkOwner ? datasetOwner : imageOwner; } chmodder = newUserInGroup(dataGroup, true); /* create a dataset */ init(datasetOwner); final Dataset dataset = (Dataset) iUpdate.saveAndReturnObject(mmFactory.simpleDataset()).proxy(); disconnect(); /* create an image */ init(imageOwner); final Image image = (Image) iUpdate.saveAndReturnObject(mmFactory.createImage()).proxy(); final long imageId = image.getId().getValue(); testImages.add(imageId); disconnect(); /* move the image into the dataset */ init(linkOwner); DatasetImageLink link = new DatasetImageLinkI(); link.setParent(dataset); link.setChild(image); link = (DatasetImageLink) iUpdate.saveAndReturnObject(link); disconnect(); /* perform the chmod */ init(chmodder); final Chmod2 chmod = Requests.chmod("ExperimenterGroup", dataGroupId, "rw----"); doChange(client, factory, chmod, true); disconnect(); /* check that exactly the expected object deletions have occurred */ logRootIntoGroup(dataGroupId); assertExists(dataset); assertExists(image); if (datasetOwner == imageOwner) { assertExists(link); } else { assertDoesNotExist(link); } disconnect(); } /** * @return a variety of test cases for container chmod */ @DataProvider(name = "chmod container test cases") public Object[][] provideChmodContainerCases() { int index = 0; final int IS_IMAGE_OWNER = index++; final int IS_LINK_OWNER = index++; final boolean[] booleanCases = new boolean[]{false, true}; final List<Object[]> testCases = new ArrayList<Object[]>(); for (final boolean isImageOwner : booleanCases) { for (final boolean isLinkOwner : booleanCases) { final Object[] testCase = new Object[index]; testCase[IS_IMAGE_OWNER] = isImageOwner; testCase[IS_LINK_OWNER] = isLinkOwner; // DEBUG: if (isImageOwner == true && isLinkOwner == true) testCases.add(testCase); } } return testCases.toArray(new Object[testCases.size()][]); } /** * Test chmod on a group wherein an image's instrument is shared with another's image. * @param toPerms the permissions on the group after the chmod * @throws Exception unexpected */ @Test(dataProvider = "private-group failure test cases") public void testSharedInstrument(String toPerms) throws Exception { /* set up the users and group for this test case */ final EventContext imageOwner, projectionOwner, chmodder; final ExperimenterGroup dataGroup; imageOwner = newUserAndGroup("rwrw--"); final long dataGroupId = imageOwner.groupId; dataGroup = new ExperimenterGroupI(dataGroupId, false); projectionOwner = newUserInGroup(dataGroup, false); chmodder = newUserInGroup(dataGroup, true); /* create an image with an instrument */ init(imageOwner); Image image = mmFactory.createImage(); image.setInstrument(mmFactory.createInstrument()); image = (Image) iUpdate.saveAndReturnObject(image); final long imageId = image.getId().getValue(); testImages.add(imageId); final Instrument instrument = (Instrument) image.getInstrument().proxy(); image = (Image) image.proxy(); disconnect(); /* another user projects the image */ init(projectionOwner); Image projection = mmFactory.createImage(); projection.setInstrument(instrument); projection = (Image) iUpdate.saveAndReturnObject(projection); final long projectionId = projection.getId().getValue(); testImages.add(projectionId); projection = (Image) projection.proxy(); disconnect(); /* perform the chmod */ final boolean isExpectSuccess = !"rw----".equals(toPerms); init(chmodder); final Chmod2 chmod = Requests.chmod("ExperimenterGroup", dataGroupId, toPerms); doChange(client, factory, chmod, isExpectSuccess); disconnect(); if (!isExpectSuccess) { return; } /* check that all the objects still exist */ logRootIntoGroup(dataGroupId); assertExists(image); assertExists(projection); assertExists(instrument); disconnect(); } /** * Test chmod on a group wherein an image is used in the same experiment as another's image. * @param toPerms the permissions on the group after the chmod * @throws Exception unexpected */ @Test(dataProvider = "private-group failure test cases") public void testSharedExperiment(String toPerms) throws Exception { /* set up the users and group for this test case */ final EventContext imageOwner, otherImageOwner, chmodder; final ExperimenterGroup dataGroup; imageOwner = newUserAndGroup("rwrw--"); final long dataGroupId = imageOwner.groupId; dataGroup = new ExperimenterGroupI(dataGroupId, false); otherImageOwner = newUserInGroup(dataGroup, false); chmodder = newUserInGroup(dataGroup, true); /* create an image with an experiment */ init(imageOwner); Image image = mmFactory.createImage(); image.setExperiment(mmFactory.simpleExperiment()); image = (Image) iUpdate.saveAndReturnObject(image); final long imageId = image.getId().getValue(); testImages.add(imageId); final Experiment experiment = (Experiment) image.getExperiment().proxy(); image = (Image) image.proxy(); disconnect(); /* another user's image is part of the same experiment */ init(otherImageOwner); Image otherImage = mmFactory.createImage(); otherImage.setExperiment(experiment); otherImage = (Image) iUpdate.saveAndReturnObject(otherImage); final long otherImageId = otherImage.getId().getValue(); testImages.add(otherImageId); otherImage = (Image) otherImage.proxy(); disconnect(); /* perform the chmod */ final boolean isExpectSuccess = !"rw----".equals(toPerms); init(chmodder); final Chmod2 chmod = Requests.chmod("ExperimenterGroup", dataGroupId, toPerms); doChange(client, factory, chmod, isExpectSuccess); disconnect(); if (!isExpectSuccess) { return; } /* check that all the objects still exist */ logRootIntoGroup(dataGroupId); assertExists(image); assertExists(otherImage); assertExists(experiment); disconnect(); } /** * Test chmod on a group wherein a plate's images are owned by a different user who has them in their dataset. * @param toPerms the permissions on the group after the chmod * @throws Exception unexpected */ @Test(dataProvider = "private-group failure test cases") public void testDatasetToPlate(String toPerms) throws Exception { /* set up the users and group for this test case */ final EventContext datasetOwner, plateOwner, chmodder; final ExperimenterGroup dataGroup; datasetOwner = newUserAndGroup("rwrw--"); final long dataGroupId = datasetOwner.groupId; dataGroup = new ExperimenterGroupI(dataGroupId, false); plateOwner = newUserInGroup(dataGroup, false); chmodder = newUserInGroup(dataGroup, true); /* create a plate */ init(plateOwner); Plate plate = mmFactory.createPlate(2, 2, 1, 1, false); plate = (Plate) iUpdate.saveAndReturnObject(plate).proxy(); final long plateId = plate.getId().getValue(); /* find the plate's images */ final List<Long> imageIds = new ArrayList<Long>(); final String hql = "SELECT image.id FROM WellSample where well.id IN (SELECT id FROM Well WHERE plate.id = :id)"; final Parameters params = new ParametersI().addId(plateId); for (final List<RType> result : iQuery.projection(hql, params)) { final Long imageId = ((RLong) result.get(0)).getValue(); imageIds.add(imageId); } disconnect(); /* the images should be owned by the dataset owner */ logRootIntoGroup(dataGroupId); final Experimenter datasetOwnerActual = new ExperimenterI(datasetOwner.userId, false); final List<IObject> images = iQuery.findAllByQuery("FROM Image WHERE id IN (:ids)", new ParametersI().addIds(imageIds)); Assert.assertEquals(images.size(), imageIds.size()); for (final IObject image : images) { image.getDetails().setOwner(datasetOwnerActual); } iUpdate.saveCollection(images); disconnect(); /* create the dataset and link the images to it */ init(datasetOwner); final Dataset dataset = (Dataset) iUpdate.saveAndReturnObject(mmFactory.simpleDataset()).proxy(); final long datasetId = dataset.getId().getValue(); final List<DatasetImageLink> links = new ArrayList<DatasetImageLink>(images.size()); for (final IObject image : images) { DatasetImageLink link = new DatasetImageLinkI(); link.setParent(dataset); link.setChild((Image) image.proxy()); links.add((DatasetImageLink) iUpdate.saveAndReturnObject(link).proxy()); } disconnect(); /* perform the chmod */ final boolean isExpectSuccess = !"rw----".equals(toPerms); init(chmodder); final Chmod2 chmod = Requests.chmod("ExperimenterGroup", dataGroupId, toPerms); doChange(client, factory, chmod, isExpectSuccess); disconnect(); logRootIntoGroup(dataGroupId); if (isExpectSuccess) { /* check that all the objects still exist */ assertExists(dataset); assertAllExist(images); assertAllExist(links); assertExists(plate); } /* delete the objects as clean-up */ final Delete2 delete = new Delete2(); delete.targetObjects = ImmutableMap.of( "Dataset", Collections.singletonList(datasetId), "Plate", Collections.singletonList(plateId)); doChange(client, factory, delete, true); disconnect(); } /** * @return group permissions for private-group failure test cases */ @DataProvider(name = "private-group failure test cases") public Object[][] providePrivateGroupFailureCases() { int index = 0; final int GROUP_PERMS = index++; final String[] permsCases = new String[]{"rw----", "rwr---", "rwra--", "rwrw--"}; final List<Object[]> testCases = new ArrayList<Object[]>(); for (final String groupPerms : permsCases) { final Object[] testCase = new Object[index]; testCase[GROUP_PERMS] = groupPerms; // DEBUG: if ("rwr---".equals(groupPerms)) testCases.add(testCase); } return testCases.toArray(new Object[testCases.size()][]); } }