/*
* $Id$
*
* Copyright 2006 University of Dundee. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.security.basic;
import static ome.model.internal.Permissions.Right.READ;
import static ome.model.internal.Permissions.Role.GROUP;
import static ome.model.internal.Permissions.Role.USER;
import static ome.model.internal.Permissions.Role.WORLD;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import ome.model.internal.Details;
import ome.model.internal.Permissions;
import ome.model.internal.Permissions.Right;
import ome.model.internal.Permissions.Role;
import ome.model.meta.ExperimenterGroup;
import ome.security.SecurityFilter;
import ome.system.EventContext;
import ome.system.Roles;
import ome.util.SqlAction;
import org.hibernate.Filter;
import org.hibernate.Session;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.orm.hibernate3.FilterDefinitionFactoryBean;
/**
* 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 AllGroupsSecurityFilter extends AbstractSecurityFilter {
static public final String is_admin = "is_admin";
static public final String member_of_groups = "member_of_groups";
static public final String leader_of_groups = "leader_of_groups";
static public final String filterName = "securityFilter";
final SqlAction sql;
/**
* 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)
*
* @param sql an SQL action instance
*/
public AllGroupsSecurityFilter(SqlAction sql) {
this(sql, new Roles());
}
public AllGroupsSecurityFilter(SqlAction sql, Roles roles) {
super(roles);
this.sql = sql;
}
protected String myFilterCondition() {
return String.format(
"\n( "
+ "\n 1 = :is_share OR "
+ "\n 1 = :is_admin OR "
+ "\n (group_id in (:leader_of_groups)) OR "
+ "\n (owner_id = :current_user AND %s) OR " // 1st arg U
+ "\n (group_id in (:member_of_groups) AND %s) OR " // 2nd arg G
+ "\n (%s) " // 3rd arg W
+ "\n)"
+ "\n", isGranted(USER, READ), isGranted(GROUP, READ),
isGranted(WORLD, READ));
}
public String getDefaultCondition() {
return myFilterCondition();
}
public Map<String, String> getParameterTypes() {
Map<String, String> parameterTypes = new HashMap<String, String>();
parameterTypes.put(is_share, "int");
parameterTypes.put(is_admin, "int");
parameterTypes.put(current_user, "long");
parameterTypes.put(member_of_groups, "long");
parameterTypes.put(leader_of_groups, "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 currentUserId = c.getCurrentUserId();
final boolean admin = c.isCurrentUserAdmin();
final boolean share = isShare(c);
final List<Long> memberOfGroups = c.getMemberOfGroupsList();
final List<Long> leaderOfGroups = c.getLeaderOfGroupsList();
final Long o = d.getOwner().getId();
final Long g = d.getGroup().getId();
// ticket:8798 - load permissions for group of object regardless.
final ExperimenterGroup group = (ExperimenterGroup) session.get(ExperimenterGroup.class, g);
Permissions p = group.getDetails().getPermissions();
if (p == null) {
// Don't know why this is happening, but must do something to
// force reloading.
p = ome.util.Utils.toPermissions(sql.getGroupPermissions(g));
group.getDetails().setPermissions(p);
log.warn(String.format(
"Forced to reload permissions for group %s: %s", g, p));
}
if (share || admin) {
return true;
}
// most likely and fastest first
if (p.isGranted(WORLD, READ)) {
return true;
}
if (currentUserId.equals(o) && p.isGranted(USER, READ)) {
return true;
}
if (memberOfGroups.contains(g)
&& p.isGranted(GROUP, READ)) {
return true;
}
if (leaderOfGroups.contains(g)) {
return true;
}
return false;
}
/***
* Since we assume that the group is "-1" for this method, we have to pass
* in lists of all groups as we did before group permissions (~4.2).
*/
public void enable(Session sess, EventContext ec) {
final Filter filter = sess.enableFilter(getName());
final int share01 = isShare(ec) ? 1 : 0;
final int admin01 = ec.isCurrentUserAdmin() ? 1 : 0;
filter.setParameter(is_admin, admin01);
filter.setParameter(SecurityFilter.is_share, share01); // ticket:2219, not checking -1 here.
filter.setParameter(SecurityFilter.current_user, ec.getCurrentUserId());
filter.setParameterList(member_of_groups,
configGroup(ec, ec.getMemberOfGroupsList()));
filter.setParameterList(leader_of_groups,
configGroup(ec, ec.getLeaderOfGroupsList()));
enableBaseFilters(sess, admin01, ec.getCurrentUserId());
}
// ~ Helpers
// =========================================================================
protected Collection<Long> configGroup(EventContext ec, List<Long> list) {
Collection<Long> rv = null;
if (ec.isCurrentUserAdmin()) {
// Admin is considered to be in every group
// which is handled by other clauses of the
// filter
rv = Collections.singletonList(-1L);
} else {
// Non-admin are only in their groups.
rv = list;
if (rv == null || rv.size() == 0) {
// If this list is empty, we have to fake something
// to prevent Hibernate from complaining.
rv = Collections.singletonList(Long.MIN_VALUE);
}
}
return rv;
}
/*
* @see ome.model.internal.Permissions#bit(Role, Right)
*/
protected static String isGranted(Role role, Right right) {
String bit = "" + Permissions.bit(role, right);
String isGranted = String
.format(
"(select (__g.permissions & %s) = %s from " +
"experimentergroup __g where __g.id = group_id)",
bit, bit);
return isGranted;
}
}