/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.security.authorization; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.builder.EqualsBuilder; import org.xwiki.model.EntityType; import static org.xwiki.security.SecurityReference.FARM; import static org.xwiki.security.authorization.RuleState.ALLOW; import static org.xwiki.security.authorization.RuleState.DENY; import static org.xwiki.security.authorization.RuleState.UNDETERMINED; /** * Enumeration of the possible rights. * @version $Id: 1bfe6527696a5e53b6ad94a0161560bf39282e15 $ * @since 4.0M2 */ public class Right implements RightDescription, Serializable, Comparable<Right> { /** The login access right. */ public static final Right LOGIN; /** The view access right. */ public static final Right VIEW; /** The edit access right. */ public static final Right EDIT; /** The delete access right. */ public static final Right DELETE; /** Imply rights provided to creator of a document. */ public static final Right CREATOR; /** The Admin access right. */ public static final Right ADMIN; /** The program access right. */ public static final Right PROGRAM; /** The script access right. */ public static final Right SCRIPT; /** The register access right. */ public static final Right REGISTER; /** The comment access right. */ public static final Right COMMENT; /** The creation of a Wiki right. */ public static final Right CREATE_WIKI; /** Illegal value. */ public static final Right ILLEGAL; /** Illegal right name. */ public static final String ILLEGAL_RIGHT_NAME = "illegal"; /** Targeted entity type list to target only the main wiki. */ public static final Set<EntityType> FARM_ONLY = null; /** Targeted entity type list to target only wikis (including main wiki). */ public static final Set<EntityType> WIKI_ONLY = EnumSet.of(EntityType.WIKI); /** Targeted entity type list to target wikis and spaces. */ public static final Set<EntityType> WIKI_SPACE = EnumSet.of(EntityType.WIKI, EntityType.SPACE); /** Targeted entity type list to target wikis, spaces and documents. */ public static final Set<EntityType> WIKI_SPACE_DOCUMENT = EnumSet.of(EntityType.WIKI, EntityType.SPACE, EntityType.DOCUMENT); /** Serialization identifier. */ private static final long serialVersionUID = 1L; /** Internal list of existing instances. */ private static final List<Right> VALUES = new ArrayList<Right>(); /** Unmodifiable list of existing instance for public dissemination. */ private static final List<Right> UNMODIFIABLE_VALUES = Collections.unmodifiableList(VALUES); /** List of all rights, as strings. */ private static final List<String> ALL_RIGHTS = new LinkedList<String>(); /** List of all rights, as strings. */ private static final List<String> UNMODIFIABLE_ALL_RIGHTS = Collections.unmodifiableList(ALL_RIGHTS); /** * The enabled rights by entity types. There is a special case hardcoded : The PROGRAM * right should only be enabled for the main wiki, not for wikis in general. */ private static final Map<EntityType, Set<Right>> ENABLED_RIGHTS = new HashMap<EntityType, Set<Right>>(); /** * The enabled rights by entity types, but with unmodifiable list for getters. */ private static final Map<EntityType, Set<Right>> UNMODIFIABLE_ENABLED_RIGHTS = new HashMap<EntityType, Set<Right>>(); static { LOGIN = new Right("login", ALLOW, ALLOW, true, null, WIKI_ONLY, true); VIEW = new Right("view", ALLOW, DENY, true, null, WIKI_SPACE_DOCUMENT, true); EDIT = new Right("edit", ALLOW, DENY, true, new RightSet(VIEW), WIKI_SPACE_DOCUMENT, false); DELETE = new Right("delete", DENY, DENY, true, null, WIKI_SPACE_DOCUMENT, false); CREATOR = new Right("creator", DENY, ALLOW, false, new RightSet(DELETE), EnumSet.of(EntityType.DOCUMENT), false); REGISTER = new Right("register", ALLOW, ALLOW, true, null, WIKI_ONLY, false); COMMENT = new Right("comment", ALLOW, DENY, true, null, WIKI_SPACE_DOCUMENT, false); SCRIPT = new Right("script", DENY, DENY, true, null, WIKI_SPACE_DOCUMENT, true); ADMIN = new Right("admin", DENY, ALLOW, false, new RightSet(LOGIN, VIEW, SCRIPT, EDIT, DELETE, REGISTER, COMMENT), WIKI_SPACE, true); CREATE_WIKI = new Right("createwiki", DENY, DENY, true, null, FARM_ONLY, false); PROGRAM = new Right("programming", DENY, ALLOW, false, new RightSet(LOGIN, VIEW, SCRIPT, EDIT, DELETE, REGISTER, COMMENT, ADMIN, CREATE_WIKI), FARM_ONLY, true); ILLEGAL = new Right(ILLEGAL_RIGHT_NAME, DENY, DENY, false, null, null, false); } /** The numeric value of this access right. */ private final int value; /** The string representation. */ private final String name; /** The string representation. */ private final RuleState defaultState; /** Whether this right should be allowed or denied in case of a tie. */ private final RuleState tieResolutionPolicy; /** Policy on how this right should be overridden by lower levels. */ private final boolean inheritanceOverridePolicy; /** Additional rights implied by this right. */ private final Set<Right> impliedRights; /** Additional rights implied by this right. */ private final boolean isReadOnly; /** * Construct a new Right from its description. * This is a package private constructor, the registration of a new right should be done using * the {@link AuthorizationManager} * * @param description Description of the right to create. */ Right(RightDescription description) { this(description.getName(), description.getDefaultState(), description.getTieResolutionPolicy(), description.getInheritanceOverridePolicy(), description.getImpliedRights(), description.getTargetedEntityType(), description.isReadOnly()); } /** * Construct a new Right. * @param name The string representation of this right. * @param defaultState The default state, in case no matching right is found at any level. * @param tieResolutionPolicy Whether this right should be allowed or denied in case of a tie. * @param inheritanceOverridePolicy Policy on how this right should be overridden by lower levels. * @param impliedRights Additional rights implied by this right. * @param validEntityTypes The type of entity where this right should be enabled. * @param isReadOnly If true, this right could be allowed when the wiki is in read-only mode. */ private Right(String name, RuleState defaultState, RuleState tieResolutionPolicy, boolean inheritanceOverridePolicy, Set<Right> impliedRights, Set<EntityType> validEntityTypes, boolean isReadOnly) { checkIllegalArguments(name, defaultState, tieResolutionPolicy); this.name = name; this.defaultState = defaultState; this.tieResolutionPolicy = tieResolutionPolicy; this.inheritanceOverridePolicy = inheritanceOverridePolicy; this.impliedRights = cloneImpliedRights(impliedRights); this.isReadOnly = isReadOnly; synchronized (VALUES) { this.value = VALUES.size(); if (this.value >= 64) { throw new IndexOutOfBoundsException(); } VALUES.add(this); if (!name.equals(ILLEGAL_RIGHT_NAME)) { ALL_RIGHTS.add(name); } if (validEntityTypes != null) { for (EntityType type : validEntityTypes) { if (type == EntityType.WIKI) { // If enabled on a wiki, enable also on main wiki. enableFor(FARM); } enableFor(type); } } else { // If enabled on a wiki, enable also on main wiki. enableFor(FARM); } } } /** * Enable this right for the given entity type. * @param type the entity type, null for the the main wiki. */ private void enableFor(EntityType type) { Set<Right> rights = ENABLED_RIGHTS.get(type); if (rights == null) { rights = new RightSet(); ENABLED_RIGHTS.put(type, rights); UNMODIFIABLE_ENABLED_RIGHTS.put(type, Collections.unmodifiableSet(rights)); } rights.add(this); } /** * Check for illegal arguments. * * @param name The string representation of this right. * @param defaultState The default state, in case no matching right is found at any level. * @param tieResolutionPolicy Whether this right should be allowed or denied in case of a tie. */ private void checkIllegalArguments(String name, RuleState defaultState, RuleState tieResolutionPolicy) { if (name == null || ALL_RIGHTS.contains(name) || (ILLEGAL != null && name.equals(ILLEGAL_RIGHT_NAME))) { throw new IllegalArgumentException(String.format("Duplicate name for right [%s]", name)); } if (defaultState == null || defaultState == UNDETERMINED) { throw new IllegalArgumentException( String.format("Invalid default state [%s] for right [%s]", defaultState, name)); } if (tieResolutionPolicy == null || tieResolutionPolicy == UNDETERMINED) { throw new IllegalArgumentException( String.format("Invalid tie resolution policy [%s] for right [%s]", tieResolutionPolicy, name)); } } /** * Clone implied Rights. * @param impliedRights the collection of rights to clone. * @return the cloned collection or null if no valid implied right has been provided. */ private Set<Right> cloneImpliedRights(Set<Right> impliedRights) { if (impliedRights == null || impliedRights.size() == 0) { return null; } Set<Right> implied = new RightSet(impliedRights); if (implied.size() > 0) { return Collections.unmodifiableSet(implied); } else { return null; } } /** * @return an unmodifiable list of available Right */ public static List<Right> values() { return UNMODIFIABLE_VALUES; } /** * Convert a string to a right. * @param string String representation of right. * @return The corresponding Right instance, or {@code ILLEGAL}. */ public static Right toRight(String string) { for (Right right : VALUES) { if (right.name.equals(string)) { return right; } } return ILLEGAL; } /** * Returns the list of rights available for a given entity type. * * @param entityType the entity type, or null for main wiki. * @return a list of {@code Right} enabled of this entity type */ public static Set<Right> getEnabledRights(EntityType entityType) { Set<Right> enabledRights = UNMODIFIABLE_ENABLED_RIGHTS.get(entityType); if (enabledRights == null) { enabledRights = Collections.<Right>emptySet(); } return enabledRights; } /** * Retrieve a right based on its ordinal. * @param ordinal the ordinal of the right * @return the {@code Right} */ public static Right get(int ordinal) { return VALUES.get(ordinal); } /** * @return the count of all existing rights */ public static int size() { return values().size(); } /** * @return a list of the string representation of all valid rights. */ public static List<String> getAllRightsAsString() { return UNMODIFIABLE_ALL_RIGHTS; } /** * @return The numeric value of this access right. */ public int ordinal() { return value; } @Override public String getName() { return name; } @Override public String toString() { return getName(); } @Override public Set<Right> getImpliedRights() { return impliedRights; } @Override public Set<EntityType> getTargetedEntityType() { List<EntityType> levels = new ArrayList<EntityType>(); for (Map.Entry<EntityType, Set<Right>> entry : ENABLED_RIGHTS.entrySet()) { if (entry.getValue().contains(this)) { levels.add(entry.getKey()); } } if (levels.contains(null) && levels.contains(EntityType.WIKI)) { levels.remove(null); } else { return null; } return EnumSet.copyOf(levels); } @Override public boolean getInheritanceOverridePolicy() { return inheritanceOverridePolicy; } @Override public RuleState getTieResolutionPolicy() { return tieResolutionPolicy; } @Override public RuleState getDefaultState() { return this.defaultState; } @Override public boolean isReadOnly() { return this.isReadOnly; } @Override public int compareTo(Right other) { return this.ordinal() - other.ordinal(); } /** * @param description a right description to compare this right to. * @return true if the right is equivalent to the provided description. */ boolean like(RightDescription description) { return new EqualsBuilder() .append(this.isReadOnly(), description.isReadOnly()) .append(this.getDefaultState(), description.getDefaultState()) .append(this.getTieResolutionPolicy(), description.getTieResolutionPolicy()) .append(this.getInheritanceOverridePolicy(), description.getInheritanceOverridePolicy()) .append(this.getTargetedEntityType(), description.getTargetedEntityType()) .append(this.getImpliedRights(), description.getImpliedRights()) .isEquals(); } }