/******************************************************************************* * Copyright (c) 2001, 2008 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.equinox.internal.useradmin; import java.util.Enumeration; import java.util.Vector; import org.osgi.service.prefs.BackingStoreException; /** * A named grouping of roles. * <p> * Whether or not a given authorization context implies a Group role * depends on the members of that role. * <p> * A Group role can have two kinds of member roles: <i>basic</i> and * <i>required</i>. * A Group role is implied by an authorization context if all of * its required member roles are implied * and at least one of its basic member roles is implied. * <p> * A Group role must contain at least one basic member role in order * to be implied. In other words, a Group without any basic member * roles is never implied by any authorization context. * <p> * A User role always implies itself. * <p> * No loop detection is performed when adding members to groups, 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> * The rule that a group must have at least one basic member to be implied * is motivated by the following example: * * <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 Group role for that matter) must have at least * one basic member accomplishes that. * <p> * However, this would make it impossible for a group 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: * * <pre> * group voter * required members: citizen, adult * basic members: * </pre> * * 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 deficiency 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: * * <pre> * group voter * required members: citizen, adult * basic members: user.anyone * </pre> */ public class Group extends User implements org.osgi.service.useradmin.Group { protected Vector requiredMembers; protected Vector basicMembers; protected Group(String name, UserAdmin useradmin) { super(name, useradmin); this.useradmin = useradmin; basicMembers = new Vector(); requiredMembers = new Vector(); } /** * Adds the specified role as a basic member to this Group. * * @param role The role to add as a basic member. * * @return <code>true</code> if the given role could be added as a basic * member, * and <code>false</code> if this Group already contains a role whose name * matches that of the specified role. * * @throws SecurityException If a security manager exists and the caller * does not have the <tt>UserAdminPermission</tt> with name <tt>admin</tt>. */ public boolean addMember(org.osgi.service.useradmin.Role role) { useradmin.checkAlive(); useradmin.checkAdminPermission(); //only need to check for null for the public methods if (role == null) { return (false); } synchronized (useradmin) { if (basicMembers.contains(role)) { return (false); } return (addMember(role, true)); } } // When we are loading from storage this method is called directly. We // do not want to write to storage when we are loading form storage. protected boolean addMember(org.osgi.service.useradmin.Role role, boolean store) { ((org.eclipse.equinox.internal.useradmin.Role) role).addImpliedRole(this); if (store) { try { useradmin.userAdminStore.addMember(this, (org.eclipse.equinox.internal.useradmin.Role) role); } catch (BackingStoreException ex) { return (false); } } basicMembers.addElement(role); return (true); } /** * Adds the specified role as a required member to this Group. * * @param role The role to add as a required member. * * @return <code>true</code> if the given role could be added as a required * member, and <code>false</code> if this Group already contains a role * whose name matches that of the specified role. * * @throws SecurityException If a security manager exists and the caller * does not have the <tt>UserAdminPermission</tt> with name <tt>admin</tt>. */ public boolean addRequiredMember(org.osgi.service.useradmin.Role role) { useradmin.checkAlive(); useradmin.checkAdminPermission(); if (role == null) { return (false); } synchronized (useradmin) { if (requiredMembers.contains(role)) { return (false); } return (addRequiredMember(role, true)); } } protected boolean addRequiredMember(org.osgi.service.useradmin.Role role, boolean store) { ((org.eclipse.equinox.internal.useradmin.Role) role).addImpliedRole(this); if (store) { try { useradmin.userAdminStore.addRequiredMember(this, (org.eclipse.equinox.internal.useradmin.Role) role); } catch (BackingStoreException ex) { return (false); } } requiredMembers.addElement(role); return (true); } /** * Removes the specified role from this Group. * * @param role The role to remove from this Group. * * @return <code>true</code> if the role could be removed, * otherwise <code>false</code>. * * @throws SecurityException If a security manager exists and the caller * does not have the <tt>UserAdminPermission</tt> with name <tt>admin</tt>. */ public boolean removeMember(org.osgi.service.useradmin.Role role) { useradmin.checkAlive(); useradmin.checkAdminPermission(); if (role == null) { return (false); } synchronized (useradmin) { try { useradmin.userAdminStore.removeMember(this, (org.eclipse.equinox.internal.useradmin.Role) role); } catch (BackingStoreException ex) { return (false); } //The role keeps track of which groups it is a member of so it can remove itself from //the group if it is deleted. In this case, this group is being removed from the role's //list. ((org.eclipse.equinox.internal.useradmin.Role) role).removeImpliedRole(this); // We don't know if the Role to be removed is a basic orrequired member, or both. We // simply try to remove it from both. boolean removeRequired = requiredMembers.removeElement(role); boolean removeBasic = basicMembers.removeElement(role); return (removeRequired || removeBasic); } } /** * Gets the basic members of this Group. * * @return The basic members of this Group, or <code>null</code> if this * Group does not contain any basic members. */ public org.osgi.service.useradmin.Role[] getMembers() { useradmin.checkAlive(); synchronized (useradmin) { if (basicMembers.isEmpty()) { return (null); } Role[] roles = new Role[basicMembers.size()]; basicMembers.copyInto(roles); return (roles); } } /** * Gets the required members of this Group. * * @return The required members of this Group, or <code>null</code> if this * Group does not contain any required members. */ public org.osgi.service.useradmin.Role[] getRequiredMembers() { useradmin.checkAlive(); synchronized (useradmin) { if (requiredMembers.isEmpty()) { return (null); } Role[] roles = new Role[requiredMembers.size()]; requiredMembers.copyInto(roles); return (roles); } } /** * Returns the type of this role. * * @return The role's type. */ public int getType() { useradmin.checkAlive(); return (org.osgi.service.useradmin.Role.GROUP); } protected boolean isImpliedBy(Role role, Vector checkLoop) { if (checkLoop.contains(name)) { //we have a circular dependency return (false); } if (name.equals(role.getName())) //A User always implies itself. A Group is a User. { return (true); } checkLoop.addElement(name); Vector requiredCheckLoop = (Vector) checkLoop.clone(); Vector basicCheckLoop = (Vector) checkLoop.clone(); Enumeration e = requiredMembers.elements(); //check to see if we imply all of the 0 or more required roles Role requiredRole; while (e.hasMoreElements()) { requiredRole = (Role) e.nextElement(); if (!requiredRole.isImpliedBy(role, requiredCheckLoop)) { return (false); } } //check to see if we imply any of the basic roles (there must be at least one) e = basicMembers.elements(); Role basicRole; while (e.hasMoreElements()) { basicRole = (Role) e.nextElement(); if (basicRole.isImpliedBy(role, basicCheckLoop)) { return (true); } } return (false); } }