/* * 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.internal; import java.util.Collection; import java.util.Deque; import java.util.Set; import org.xwiki.model.EntityType; import org.xwiki.security.GroupSecurityReference; import org.xwiki.security.SecurityReference; import org.xwiki.security.UserSecurityReference; import org.xwiki.security.authorization.AuthorizationSettler; import org.xwiki.security.authorization.Right; import org.xwiki.security.authorization.RightSet; import org.xwiki.security.authorization.RuleState; import org.xwiki.security.authorization.SecurityAccess; import org.xwiki.security.authorization.SecurityAccessEntry; import org.xwiki.security.authorization.SecurityRuleEntry; /** * Abstract super class for right resolvers. * @version $Id: 5ee96de6b6337fdb5a1d91de43f79fdf9ce7e3ec $ * @since 4.0M2 */ abstract class AbstractAuthorizationSettler implements AuthorizationSettler { /** Cache the initial tie resolution. */ private static RightSet initialAllowTie; /** Cache the initial no override inheritance policy. */ private static RightSet initialNoOverride; /** The initial policy size. Check to update initial policies if a new Right is added. */ private static int initialPolicySize; /** * Private implementation of the {@link SecurityAccessEntry}. */ private final class InternalSecurityAccessEntry extends AbstractSecurityAccessEntry { /** User reference. */ private final UserSecurityReference userReference; /** Entity reference. */ private final SecurityReference reference; /** Security access. */ private final SecurityAccess access; /** * @param user User reference * @param reference Entity reference * @param access access */ InternalSecurityAccessEntry(UserSecurityReference user, SecurityReference reference, SecurityAccess access) { this.userReference = user; this.reference = reference; this.access = access; } @Override public UserSecurityReference getUserReference() { return this.userReference; } @Override public SecurityAccess getAccess() { return this.access; } @Override public SecurityReference getReference() { return this.reference; } } /** * Current policies helper. */ protected final class Policies { /** Current right which has an allow tie. */ private Set<Right> allowTie; /** Current right which has an no override inheritance policy. */ private Set<Right> noOverride; /** * Create Policies based on default initial policies. */ Policies() { try { if (initialAllowTie == null || Right.size() != initialPolicySize) { initialPolicySize = Right.size(); allowTie = new RightSet(); noOverride = new RightSet(); for (Right right : Right.values()) { set(right, right); } initialAllowTie = ((RightSet) allowTie).clone(); initialNoOverride = ((RightSet) noOverride).clone(); } else { allowTie = initialAllowTie.clone(); noOverride = initialNoOverride.clone(); } } catch (CloneNotSupportedException ignored) { // unexpected } } /** * Set the current tie and inheritance policy of an implied right * to the policies of the original right. * Once allowed, the resolution policy could not be denied. * Once a no override policy set, it could not be revoked. * * @param impliedRight the implied right to set * @param originalRight the original right to get */ public void set(Right impliedRight, Right originalRight) { if (originalRight.getTieResolutionPolicy() == RuleState.ALLOW) { allowTie.add(impliedRight); } if (!originalRight.getInheritanceOverridePolicy()) { noOverride.add(impliedRight); } } /** * @param right the right to check. * @return the current tie resolution policy of this right. */ public RuleState getTieResolutionPolicy(Right right) { return (allowTie.contains(right)) ? RuleState.ALLOW : RuleState.DENY; } /** * @param right the right to check. * @return the current tie resolution policy of this right. */ public boolean getInheritanceOverridePolicy(Right right) { return !noOverride.contains(right); } } @Override public SecurityAccessEntry settle(UserSecurityReference user, Collection<GroupSecurityReference> groups, Deque<SecurityRuleEntry> ruleEntries) { XWikiSecurityAccess access = new XWikiSecurityAccess(); SecurityReference reference = null; Policies policies = new Policies(); for (SecurityRuleEntry entry : ruleEntries) { if (!entry.isEmpty()) { // Chose the highest possible level to store the resulting access if (reference == null) { reference = entry.getReference(); } // Compute access of this level and merge it with previous access result merge(settle(user, groups, entry, policies), access, entry.getReference(), policies); } if (reference == null && entry.getReference().getType() == EntityType.WIKI) { reference = entry.getReference(); } } // Apply defaults and return the resulting access entry return new InternalSecurityAccessEntry(user, reference, applyDefaults(user, reference, access)); } /** * Apply default values for undetermined rights. * * @param user The user, whose rights are to be determined. * @param reference The entity, which the user wants to access. * @param access The accumulated access result (modified and returned). * @return the accumulated access result. */ protected XWikiSecurityAccess applyDefaults(UserSecurityReference user, SecurityReference reference, XWikiSecurityAccess access) { for (Right right : Right.values()) { if (access.get(right) == RuleState.UNDETERMINED) { if (!user.isGlobal() && !user.getOriginalReference().getWikiReference() .equals(reference.extractReference(EntityType.WIKI))) { /* * Deny all by default for users from another wiki. */ access.deny(right); } else { access.set(right, right.getDefaultState()); } } } return access; } /** * Compute the access of a particular document hierarchy level. * @param user The user. * @param groups The groups where the user is a member. * @param entry The security entry to settle. * @param policies the current security policies. * @return the resulting access for the user/group based on the given rules. */ protected abstract XWikiSecurityAccess settle(UserSecurityReference user, Collection<GroupSecurityReference> groups, SecurityRuleEntry entry, Policies policies); /** * Merge the current access with the result from previous ones. * @param currentAccess The access computed at the current entity in the document hierarchy. * @param access The resulting access previously computed (modified and returned). * @param reference the current entity in the document hierarchy. * @param policies the current security policies. */ protected void merge(SecurityAccess currentAccess, XWikiSecurityAccess access, SecurityReference reference, Policies policies) { for (Right right : Right.getEnabledRights(reference.getSecurityType())) { // Skip undetermined rights if (currentAccess.get(right) == RuleState.UNDETERMINED) { continue; } if (access.get(right) == RuleState.UNDETERMINED) { access.set(right, currentAccess.get(right)); continue; } if (currentAccess.get(right) == RuleState.ALLOW && !policies.getInheritanceOverridePolicy(right)) { access.allow(right); } } } }