/*
* $Id$
*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ome.api.local.LocalAdmin;
import ome.conditions.SecurityViolation;
import ome.model.IObject;
import ome.model.internal.Permissions;
import ome.model.meta.Event;
import ome.model.meta.EventLog;
import ome.model.meta.Experimenter;
import ome.model.meta.ExperimenterGroup;
import ome.services.messages.RegisterServiceCleanupMessage;
import ome.services.sessions.stats.SessionStats;
import ome.services.sharing.ShareStore;
import ome.services.sharing.data.ShareData;
import ome.system.EventContext;
import ome.system.Principal;
import ome.system.SimpleEventContext;
/**
* {@link EventContext} implementation for use within the security system. Holds
* various other information needed for proper functioning of a {@link Thread}.
*
* Not-thread-safe. Intended to be held by a {@link ThreadLocal}
*/
public class BasicEventContext extends SimpleEventContext {
private final static Logger log = LoggerFactory.getLogger(BasicEventContext.class);
// Additions beyond simple event context
// =========================================================================
/**
* Prinicpal should only be set once (on
* {@link PrincipalHolder#login(Principal)}.
*/
private final Principal p;
private final SessionStats stats;
private Set<String> disabledSubsystems;
private Set<RegisterServiceCleanupMessage> serviceCleanups;
private Set<IObject> lockCandidates;
private List<EventLog> logs;
private Event event;
private Experimenter owner;
private ExperimenterGroup group;
private Map<String, String> callContext;
private Map<Long, Permissions> groupPermissionsMap;
public BasicEventContext(Principal p, SessionStats stats) {
if (p == null || stats == null) {
throw new RuntimeException("Principal and stats canot be null.");
}
this.p = p;
this.stats = stats;
}
/**
* Copy-constructor to not have to allow the mutator {@link #copy(EventContext)}
* or {@code copyContext(EventContext)} out of the {@link EventContext}
* hierarchy.
*/
public BasicEventContext(Principal p, SessionStats stats, EventContext ec) {
this(p, stats);
copyContext(ec);
}
void invalidate() {
owner = null;
group = null;
event = null;
}
/**
* Making {@link SimpleEventContext#copy(EventContext)} available to
* package-private classes.
*/
void copyContext(EventContext ec) {
super.copy(ec);
}
void checkAndInitialize(EventContext ec, LocalAdmin admin, ShareStore store) {
this.copyContext(ec);
// Now re-apply values.
List<String> toPrint = null;
final Long sid = parseId(callContext, "omero.share");
if (sid != null) {
if (!isAdmin) {
// If the user is not an admin then we need to verify that
// s/he is a valid member of the share.
ShareData data = store.getShareIfAccessible(sid, isAdmin, this.cuId);
if (data == null) {
throw new SecurityViolation(String.format(
"User %s cannot access share %s", this.cuId, sid));
}
}
setShareId(sid);
if (toPrint == null) {
toPrint = new ArrayList<String>();
}
toPrint.add("share="+sid);
return; // IGNORE all other settings for share (#8608)
}
final Long uid = parseId(callContext, "omero.user");
if (uid != null) {
// Here we trust the setting of the admin flag if we also have
// a user setting. In other words, if this has been initialized
// by the session context, then it's safe to say that we're just
// overwriting values.
if (cuId != null && !isAdmin && !cuId.equals(uid)) {
throw new SecurityViolation(String.format(
"User %s is not an admin and so cannot set uid to %s",
cuId, uid));
}
setOwner(admin.userProxy(uid));
if (toPrint == null) {
toPrint = new ArrayList<String>();
}
toPrint.add("owner="+uid);
}
final Long gid = parseId(callContext, "omero.group");
if (gid != null) {
if (gid < 0) {
setGroup(new ExperimenterGroup(gid, false), Permissions.DUMMY);
} else {
ExperimenterGroup g = admin.groupProxy(gid);
setGroup(g, g.getDetails().getPermissions());
}
if (toPrint == null) {
toPrint = new ArrayList<String>();
}
toPrint.add("group="+gid);
}
if (toPrint != null && toPrint.size() > 0) {
log.info(" cctx:\t" + StringUtils.join(toPrint, ","));
}
}
// Call Context (ticket:3529)
// =========================================================================
static Long parseId(Map<String, String> ctx, String key) {
Long rv = null;
if (ctx != null && ctx.containsKey(key)) {
String s = ctx.get(key);
try {
rv = Long.valueOf(ctx.get(key));
log.debug("Using call requested group: " + key + "=" + s);
} catch (Exception e) {
log.warn("Ignoring invalid requested group: " + key + "=" + s);
}
}
return rv;
}
public Map<String, String> getCallContext() {
return callContext;
}
public Map<String, String> setCallContext(Map<String, String> ctx) {
final Map<String, String> rv = callContext;
callContext = ctx;
return rv;
}
// ~ Getters/Setters for superclass state
// =========================================================================
public void setUmask(Permissions umask) {
this.umask = umask;
}
public void setAdmin(boolean admin) {
this.isAdmin = admin;
}
public void setReadOnly(boolean readOnly) {
this.isReadOnly = readOnly;
}
public void setShareId(Long id) {
this.shareId = id;
}
// ~ Accessors for other state
// =========================================================================
public Principal getPrincipal() {
return p;
}
public SessionStats getStats() {
return stats;
}
public Event getEvent() {
return event;
}
public void setEvent(Event event) {
this.event = event;
this.ceId = event.getId();
if (event.isLoaded()) {
if (event.getType().isLoaded()) {
this.ceType = event.getType().getValue();
}
}
}
public Experimenter getOwner() {
return owner;
}
public void setOwner(Experimenter owner) {
this.owner = owner;
this.cuId = owner.getId();
if (owner.isLoaded()) {
this.cuName = owner.getOmeName();
}
}
public ExperimenterGroup getGroup() {
return group;
}
public void setGroup(ExperimenterGroup group, Permissions p) {
this.group = group;
setGroupPermissions(p);
if (this.cgId.equals(group.getId())) {
// Do nothing.
} else {
this.cgId = group.getId();
this.cgName = null;
// If unloaded or group.id < -1 these will remain null
if (group.isLoaded()) {
this.cgName = group.getName();
}
}
}
public Set<String> getDisabledSubsystems() {
return disabledSubsystems;
}
public void setDisabledSubsystems(Set<String> disabledSubsystems) {
this.disabledSubsystems = disabledSubsystems;
}
public Set<RegisterServiceCleanupMessage> getServiceCleanups() {
return serviceCleanups;
}
public void setServiceCleanups(
Set<RegisterServiceCleanupMessage> serviceCleanups) {
this.serviceCleanups = serviceCleanups;
}
public Set<IObject> getLockCandidates() {
return lockCandidates;
}
public void setLockCandidates(Set<IObject> lockCandidates) {
this.lockCandidates = lockCandidates;
}
public List<EventLog> getLogs() {
return logs;
}
public void setLogs(List<EventLog> logs) {
this.logs = logs;
}
// ~ Special logic for groups
// =========================================================================
@Override
public List<Long> getMemberOfGroupsList() {
return memberOfGroups;
}
@Override
public List<Long> getLeaderOfGroupsList() {
return leaderOfGroups;
}
public void setMemberOfGroups(List<Long> groupIds) {
this.memberOfGroups = groupIds;
}
public void setLeaderOfGroups(List<Long> groupIds) {
this.leaderOfGroups = groupIds;
}
// Other
// =========================================================================
/**
* Never returns {@link Permissions#DUMMY}.
* @param group a group ID, may be {@code null}
* @return the group's permissions, may be {@code null}
*/
public Permissions getPermissionsForGroup(Long group) {
if (group == null || groupPermissionsMap == null) {
return null;
}
return groupPermissionsMap.get(group);
}
/**
* Called during {@link BasicACLVoter#allowLoad(org.hibernate.Session, Class, ome.model.internal.Details, long)}
* to track groups that will need resolving later.
* @param group a group ID, not {@code null}
* @param perms the group's permissions
* @return the group's previous permissions, or {@code null} if none are noted
*/
public Permissions setPermissionsForGroup(Long group, Permissions perms) {
if (perms == Permissions.DUMMY) {
throw new ome.conditions.InternalException(
"DUMMY permissions passed to setPermissionsForGroup!");
}
if (groupPermissionsMap == null) {
groupPermissionsMap = new HashMap<Long, Permissions>();
}
return groupPermissionsMap.put(group, perms);
}
public void loadPermissions(org.hibernate.Session session) {
if (groupPermissionsMap != null) {
for (Map.Entry<Long, Permissions> entry :
groupPermissionsMap.entrySet()) {
if (entry.getValue() == null) {
Long id = entry.getKey();
ExperimenterGroup g = (ExperimenterGroup)
session.get(ExperimenterGroup.class, id);
entry.setValue(g.getDetails().getPermissions());
}
}
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append("(");
sb.append("Principal:" + p);
sb.append(";");
sb.append(this.owner);
sb.append(";");
sb.append(this.group);
sb.append(";");
sb.append(this.event);
sb.append(";");
sb.append("ReadOnly:" + this.isReadOnly);
sb.append(")");
return sb.toString();
}
}