/*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.security.basic;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.orm.hibernate3.FilterDefinitionFactoryBean;
import ome.conditions.InternalException;
import ome.model.internal.Details;
import ome.model.internal.Permissions;
import ome.model.internal.Permissions.Right;
import ome.model.internal.Permissions.Role;
import ome.security.SecurityFilter;
import ome.system.EventContext;
import ome.system.Roles;
/**
* overrides {@link FilterDefinitionFactoryBean} in order to construct our
* security filter in code and not in XML. This allows us to make use of the
* knowledge within {@link Permissions}
*
* With the addition of shares in 4.0, it is necessary to remove the security
* filter if a share is active and allow loading to throw the necessary
* exceptions.
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0
* @see <a
* href="http://trac.openmicroscopy.org.uk/ome/ticket/117">ticket117</a>
* @see <a
* href="http://trac.openmicroscopy.org.uk/ome/ticket/1154">ticket1154</a>
*/
public class OneGroupSecurityFilter extends AbstractSecurityFilter {
static public final String current_group = "current_group";
/**
* Default constructor which calls all the necessary setters for this
* {@link FactoryBean}. Also calls {@link #setDefaultFilterCondition(String)}.
* This query clause must be kept in sync with
* {@link #passesFilter(Session, Details, EventContext)}.
*
* @see #passesFilter(Session, Details, EventContext)
* @see FilterDefinitionFactoryBean#setFilterName(String)
* @see FilterDefinitionFactoryBean#setParameterTypes(java.util.Map)
* @see FilterDefinitionFactoryBean#setDefaultFilterCondition(String)
*/
public OneGroupSecurityFilter() {
super();
}
public OneGroupSecurityFilter(Roles roles) {
super(roles);
}
protected String myFilterCondition() {
return "(\n"
// Should handle hidden groups at the top-level
// ticket:1784 - Allowing system objects to be read.
+ "\n ( group_id = :current_group AND "
+ "\n ( 1 = :is_nonprivate OR "
+ "\n 1 = :is_adminorpi OR "
+ "\n owner_id = :current_user"
+ "\n )"
+ "\n ) OR"
+ "\n group_id = %s OR " // ticket:1794
// Will need to add something about world readable here.
+ "\n 1 = :is_share"
+ "\n)\n";
}
public String getDefaultCondition() {
return String.format(myFilterCondition(), roles.getUserGroupId());
}
public Map<String, String> getParameterTypes() {
Map<String, String> parameterTypes = new HashMap<String, String>();
parameterTypes.put(is_share, "int");
parameterTypes.put(is_adminorpi, "int");
parameterTypes.put(is_nonprivate, "int");
parameterTypes.put(current_group, "long");
parameterTypes.put(current_user, "long");
return parameterTypes;
}
/**
* tests that the {@link Details} argument passes the security test that
* this filter defines. The two must be kept in sync. This will be used
* mostly by the
* {@link OmeroInterceptor#onLoad(Object, java.io.Serializable, Object[], String[], org.hibernate.type.Type[])}
* method.
*
* @param d
* Details instance. If null (or if its {@link Permissions} are
* null all {@link Right rights} will be assumed.
* @return true if the object to which this
*/
public boolean passesFilter(Session session, Details d, EventContext c) {
final Long currentGroupId = c.getCurrentGroupId();
final Long currentUserId = c.getCurrentUserId();
final boolean nonPrivate = isNonPrivate(c);
final boolean adminOrPi = isAdminOrPi(c);
final boolean share = isShare(c);
final List<Long> memberOfGroups = c.getMemberOfGroupsList();
if (d == null || d.getPermissions() == null) {
throw new InternalException("Details/Permissions null! "
+ "Security system failure -- refusing to continue. "
+ "The Permissions should be set to a default value.");
}
Long o = d.getOwner().getId();
Long g = d.getGroup().getId();
if (share) {
return true;
}
// ticket:1434 - Only loading current objects is permitted.
// This method will not be called with system types.
// See BasicACLVoter
// Also ticket:1784 allowing system objects to be read.
// Also ticket:1791 allowing user objects to be read (also 1794)
if (Long.valueOf(roles.getSystemGroupId()).equals(g) ||
Long.valueOf(roles.getUserGroupId()).equals(g)) {
return true;
}
if (currentGroupId < 0) {
throwNegOne();
} else if (!currentGroupId.equals(g)) {
return false;
}
if (nonPrivate) {
return true;
}
if (adminOrPi) {
return true;
}
if (currentUserId.equals(o)) {
return true;
}
return false;
}
public void enable(Session sess, EventContext ec) {
final Filter filter = sess.enableFilter(getName());
final Long groupId = ec.getCurrentGroupId();
final Long shareId = ec.getCurrentShareId();
int share01 = shareId != null ? 1 : 0; // non-final; "ticket:3529" below
final int adminOrPi01 = (ec.isCurrentUserAdmin() ||
ec.getLeaderOfGroupsList().contains(ec.getCurrentGroupId()))
? 1 : 0;
final int nonpriv01 = (ec.getCurrentGroupPermissions().isGranted(Role.GROUP, Right.READ)
|| ec.getCurrentGroupPermissions().isGranted(Role.WORLD, Right.READ))
? 1 : 0;
if (groupId < 0) { // Special marker
throwNegOne();
}
filter.setParameter(SecurityFilter.is_share, share01); // ticket:2219, not checking -1 here.
filter.setParameter(SecurityFilter.is_adminorpi, adminOrPi01);
filter.setParameter(SecurityFilter.is_nonprivate, nonpriv01);
filter.setParameter(SecurityFilter.current_user, ec.getCurrentUserId());
filter.setParameter(current_group, groupId);
enableBaseFilters(sess, ec.isCurrentUserAdmin() ? 1 : 0, ec.getCurrentUserId());
}
private void throwNegOne() {
throw new InternalException("OneGroupSecurityFilter is not " +
"capable of handling omero.group=-1. This is handled by " +
"AllGroupsSecurityFilter");
}
}