/******************************************************************************* * Copyright (c) 2012 RelationWare, Benno Luthiger * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * RelationWare, Benno Luthiger ******************************************************************************/ package org.ripla.useradmin.admin; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.useradmin.Group; import org.osgi.service.useradmin.Role; /** * <p> * A named grouping of roles (<tt>Role</tt> objects). * </p> * <p> * Whether or not a given <tt>Authorization</tt> context implies a * <tt>Group</tt> object depends on the members of that <tt>Group</tt> object. * </p> * <p> * A <tt>Group</tt> object can have two kinds of members: <i>basic</i> and * <i>required</i>. A <tt>Group</tt> object is implied by an * <tt>Authorization</tt> context if all of its required members are implied and * at least one of its basic members is implied. * </p> * <p> * A <tt>Group</tt> object must contain at least one basic member in order to be * implied. In other words, a <tt>Group</tt> object without any basic member * roles is never implied by any <tt>Authorization</tt> context. * </p> * <p> * A <tt>User</tt> object always implies itself. * </p> * <p> * No loop detection is performed when adding members to <tt>Group</tt> objects, * which means that it is possible to create circular implications. Loop * detection is instead done when roles are checked. The semantics is that if a * role depends on itself (i.e., there is an implication loop), the role is not * implied. * </p> * <p> * The rule that a <tt>Group</tt> object must have at least one basic member to * be implied is motivated by the following example: * </p> * * <pre> * group foo * required members: marketing * basic members: alice, bob * </pre> * * Privileged operations that require membership in "foo" can be performed only * by "alice" and "bob", who are in marketing. * <p> * If "alice" and "bob" ever transfer to a different department, anybody in * marketing will be able to assume the "foo" role, which certainly must be * prevented. Requiring that "foo" (or any <tt>Group</tt> object for that * matter) must have at least one basic member accomplishes that. * </p> * <p> * However, this would make it impossible for a <tt>Group</tt> object to be * implied by just its required members. An example where this implication might * be useful is the following declaration: "Any citizen who is an adult is * allowed to vote." An intuitive configuration of "voter" would be: * * </p> * * <pre> * group voter * required members: citizen, adult * basic members: * </pre> * * <p> * However, according to the above rule, the "voter" role could never be assumed * by anybody, since it lacks any basic members. In order to address this issue * a predefined role named "user.anyone" can be specified, which is always * implied. The desired implication of the "voter" group can then be achieved by * specifying "user.anyone" as its basic member, as follows: * </p> * * <pre> * group voter * required members: citizen, adult * basic members: user.anyone * </pre> * * @author Luthiger */ public class RiplaGroup extends RiplaUser implements Group { private final transient RiplaUserAdmin userAdmin; private final transient Collection<Role> requiredMembers; private final transient Collection<Role> basicMembers; /** * @param inName * @param inType */ public RiplaGroup(final String inName, final RiplaUserAdmin inUserAdmin) { super(inName, inUserAdmin); userAdmin = inUserAdmin; requiredMembers = new ArrayList<Role>(); basicMembers = new ArrayList<Role>(); } @Override public boolean addMember(final Role inRole) { if (!checkPre(inRole)) { return false; } synchronized (userAdmin) { if (basicMembers.contains(inRole)) { return false; } return addMember(inRole, true); } } public boolean addMember(final Role inRole, final boolean inStore) { ((RiplaRole) inRole).addImpliedRole(this); if (inStore) { try { userAdmin.getUserAdminStore().addMember(this, inRole); } catch (final BackingStoreException exc) { return false; } } basicMembers.add(inRole); return true; } @Override public boolean addRequiredMember(final Role inRole) { if (!checkPre(inRole)) { return false; } synchronized (userAdmin) { if (requiredMembers.contains(inRole)) { return false; } return addRequiredMember(inRole, true); } } public boolean addRequiredMember(final Role inRole, final boolean inStore) { ((RiplaRole) inRole).addImpliedRole(this); if (inStore) { try { userAdmin.getUserAdminStore().addRequiredMember(this, inRole); } catch (final BackingStoreException exc) { return false; } } requiredMembers.add(inRole); return true; } @Override public boolean removeMember(final Role inRole) { if (!checkPre(inRole)) { return false; } synchronized (userAdmin) { try { userAdmin.getUserAdminStore().removeMember(this, inRole); } catch (final BackingStoreException exc) { return false; } ((RiplaRole) inRole).removeImpliedRole(this); final boolean lRemoveBasic = basicMembers.remove(inRole); final boolean lRemoveRequired = requiredMembers.remove(inRole); return lRemoveBasic || lRemoveRequired; } } private boolean checkPre(final Role inRole) { userAdmin.checkAlive(); userAdmin.checkAdminPermission(); return inRole != null; } @Override public Role[] getMembers() { return getMemers(basicMembers); } @Override public Role[] getRequiredMembers() { return getMemers(requiredMembers); } private Role[] getMemers(final Collection<Role> inMembers) { userAdmin.checkAlive(); synchronized (userAdmin) { if (inMembers.isEmpty()) { return null; } final Role[] outRoles = new Role[inMembers.size()]; int i = 0; // NOPMD by Luthiger on 07.09.12 00:11 for (final Role lRole : inMembers) { outRoles[i++] = lRole; } return outRoles; } } @Override public int getType() { userAdmin.checkAlive(); return GROUP; } @Override @SuppressWarnings("unchecked") protected boolean isImpliedBy(final Role inRole, final List<String> inCheckLoop) { if (inCheckLoop.contains(getName())) { // we have a circular dependency return false; } if (getName().equals(inRole.getName())) // A User always implies itself. // A Group is a User. { return true; } inCheckLoop.add(getName()); final ArrayList<String> lRequiredCheckLoop = (ArrayList<String>) ((ArrayList<String>) inCheckLoop).clone(); final ArrayList<String> lBasicCheckLoop = (ArrayList<String>) ((ArrayList<String>) inCheckLoop).clone(); // check to see if we imply all of the 0 or more required roles Iterator<Role> lRoles = requiredMembers.iterator(); RiplaRole lRequiredRole; while (lRoles.hasNext()) { lRequiredRole = (RiplaRole) lRoles.next(); if (!lRequiredRole.isImpliedBy(inRole, lRequiredCheckLoop)) { return false; } } // check to see if we imply any of the basic roles (there must be at // least one) lRoles = basicMembers.iterator(); RiplaRole lBasicRole; while (lRoles.hasNext()) { lBasicRole = (RiplaRole) lRoles.next(); if (lBasicRole.isImpliedBy(inRole, lBasicCheckLoop)) { return true; } } return false; } @Override public String toString() { return String.format("Group: %s", getName()); } }