/*
* 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 ome.security.policy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import ome.conditions.SecurityViolation;
import ome.model.IObject;
import ome.model.core.Image;
import ome.model.core.OriginalFile;
import ome.model.fs.Fileset;
import ome.model.fs.FilesetEntry;
import ome.model.internal.NamedValue;
import ome.model.meta.ExperimenterGroup;
import ome.model.screen.Plate;
import ome.model.screen.PlateAcquisition;
import ome.model.screen.Well;
import ome.model.screen.WellSample;
import ome.security.ACLVoter;
import org.hibernate.AssertionFailure;
import org.hibernate.Hibernate;
/**
* Policy which should be checked anytime access to original binary files in
* OMERO is being attempted. This check is <em>in addition</em> to the
* standard permission permission and is intended to allow customizing who
* has access to widely shared data.
*/
public class BinaryAccessPolicy extends BasePolicy {
/**
* This string can also be found in the Constants.ice file in the
* blitz package.
*/
public final static String NAME = "RESTRICT-BINARY-ACCESS";
private final ACLVoter voter;
private final Set<String> global;
public BinaryAccessPolicy(Set<Class<IObject>> types, ACLVoter voter) {
this(types, voter, null);
}
public BinaryAccessPolicy(Set<Class<IObject>> types, ACLVoter voter,
String[] config) {
super(types);
this.voter = voter;
if (config == null) {
this.global = Collections.emptySet();
} else {
this.global = new HashSet<String>(Arrays.asList(config));
}
}
@Override
public String getName() {
return NAME;
}
@Override
public boolean isRestricted(IObject obj) {
final Set<String> group= groupRestrictions(obj);
if (notAorB("+write", "-write", group)) {
// effectively "None"
return true;
} else if (notAorB("+read", "-read", group)) {
if (!voter.allowUpdate(obj, obj.getDetails())) {
return true;
}
}
final boolean noImage = notAorB("+image", "-image", group);
final boolean noPlate = notAorB("+plate", "-plate", group);
// Possible performance impact!
if (obj instanceof OriginalFile) {
OriginalFile ofile = (OriginalFile) obj;
// Quick short-cut for necessary files
boolean isTxt = ofile.getName().endsWith(".txt");
if (isTxt) {
return false;
}
Iterator<FilesetEntry> it = ofile.iterateFilesetEntries();
while (it.hasNext()) {
FilesetEntry fe = it.next();
if (fe != null && fe.getFileset() != null) {
Fileset f = fe.getFileset();
if (has(f, Fileset.IMAGES)) {
if (noImage) {
return true;
} else if (noPlate) {
Iterator<Image> it2 = f.iterateImages();
while (it2.hasNext()) {
Image img = it2.next();
if (img != null) {
if (has(img, Image.WELLSAMPLES)) {
return true;
}
}
}
}
}
}
}
} else if (obj instanceof Image) {
if (noImage) {
return true;
}
// If an Image has a WellSample, then *also* perform the plate check
// Note: checking noPlate first since it doesn't need to hit the DB.
if (noPlate) {
Image img = (Image) obj;
if (has(img, Image.WELLSAMPLES)) {
return true;
}
}
} else if (obj instanceof Plate ||
obj instanceof PlateAcquisition ||
obj instanceof Well ||
obj instanceof WellSample) {
if (noImage || noPlate) {
return true;
}
}
return false;
}
protected Set<String> groupRestrictions(IObject obj) {
ExperimenterGroup grp = obj.getDetails().getGroup();
if (grp != null && grp.getConfig() != null && grp.getConfig().size() > 0) {
Set<String> rv = null;
for (NamedValue nv : grp.getConfig()) {
if ("omero.policy.binary_access".equals(nv.getName())) {
if (rv == null) {
rv = new HashSet<String>();
}
String setting = nv.getValue();
rv.add(setting);
}
}
if (rv != null) {
return rv;
}
}
return Collections.emptySet();
}
/**
* Returns true if the minus argument is present in the configuration
* collections <em>or</em> if the plus argument is not present.
*/
private final boolean notAorB(String plus, String minus, Collection<String> group) {
if (global.contains(minus) || group.contains(minus)) {
return true;
} else if (global.contains(plus) || group.contains(plus)) {
return false;
}
return true;
}
/**
* Test if the size of the given collection is loadable and more than 0.
*
* If an {@link AssertionFailure} is thrown, we assume that someone else
* is trying to load the {@link IObject} at the same time. Since the
* flag will be set on an earlier {@link IObject}, we assume that
* an actual download won't be attempted. If it is, then the policy will
* properly load this {@link IObject} and throw a SecurityViolation.
*/
private boolean has(IObject obj, String field) {
try {
Collection<?> c = (Collection<?>) obj.retrieve(field);
Hibernate.initialize(c);
if (c != null && !c.isEmpty()) {
return true;
}
} catch (AssertionFailure ae) {
// pass
}
return false;
}
@Override
public void checkRestriction(IObject obj) {
if (isRestricted(obj)) {
throw new SecurityViolation(String.format(
"Download is restricted for %s",
obj));
}
}
}