/*
* 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.Map;
import java.util.Set;
import javax.inject.Named;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.security.GroupSecurityReference;
import org.xwiki.security.SecurityReference;
import org.xwiki.security.UserSecurityReference;
import org.xwiki.security.authorization.Right;
import org.xwiki.security.authorization.RightMap;
import org.xwiki.security.authorization.RuleState;
import org.xwiki.security.authorization.SecurityRule;
import org.xwiki.security.authorization.SecurityRuleEntry;
import static org.xwiki.security.authorization.RuleState.ALLOW;
import static org.xwiki.security.authorization.RuleState.UNDETERMINED;
/**
* An implementation for the {@link org.xwiki.security.authorization.AuthorizationSettler}.
* Provide similar decision as the old xwiki right service, but consider rules at the same
* level by prioritizing user rules, over group rules and all group rules in this order.
*
* IMPORTANT NOTE: This experimental settler is current unmaintained and untested. Use at you own risk.
*
* @version $Id: 3035132a2698f05158fb73c0d1a044fa4a0b5566 $
* @since 4.0M2
*/
@Component
@Named("priority")
@Singleton
public class PrioritizingAuthorizationSettler extends AbstractAuthorizationSettler
{
/** Priority of rights specified for users. */
private static final int USER_PRIORITY = Integer.MAX_VALUE;
/** Priority of rights specified for "all group". */
private static final int ALL_GROUP_PRIORITY = 0;
@Override
protected XWikiSecurityAccess settle(UserSecurityReference user, Collection<GroupSecurityReference> groups,
SecurityRuleEntry entry, Policies policies)
{
XWikiSecurityAccess access = new XWikiSecurityAccess();
Map<Right, Integer> priorities = new RightMap<Integer>();
SecurityReference reference = entry.getReference();
Set<Right> enabledRights = Right.getEnabledRights(reference.getSecurityType());
// Evaluate rules from current level
for (Right right : enabledRights) {
for (SecurityRule obj : entry.getRules()) {
if (obj.match(right)) {
resolveLevel(right, user, groups, obj, access, policies, priorities);
if (access.get(right) == ALLOW) {
implyRights(right, access, reference, policies, priorities);
}
}
}
}
return access;
}
/**
* Add implied rights of the given right into the current access.
*
* @param right the right to imply right for.
* @param access the access to be augmented (modified and returned).
* @param reference the reference to imply rights for.
* @param policies the current security policies.
* @param priorities A map of current priorities of each rights in the current accumulated access result.
*/
private void implyRights(Right right, XWikiSecurityAccess access, SecurityReference reference,
Policies policies, Map<Right, Integer> priorities)
{
Set<Right> impliedRights = right.getImpliedRights();
if (impliedRights != null) {
for (Right enabledRight : Right.getEnabledRights(reference.getSecurityType())) {
if (impliedRights.contains(enabledRight)) {
// set the policies of the implied right to the policies of the original right
policies.set(enabledRight, right);
resolveConflict(ALLOW, enabledRight, access, policies, priorities.get(right), priorities);
}
}
}
}
/**
* Update the resulting {@code access} to include the rule state defined by the given {@link SecurityRule}
* for the given user and group, and the requested {@link Right}.
*
* @param right The right to settle.
* @param user The user to check.
* @param groups The groups where the user is a member.
* @param rule The currently considered rule.
* @param access The accumulated access result during interpretation of rules.
* @param policies the current security policies.
* @param priorities A map of current priorities of each rights in the current accumulated access result.
*
*/
private void resolveLevel(Right right,
UserSecurityReference user, Collection<GroupSecurityReference> groups, SecurityRule rule,
XWikiSecurityAccess access, Policies policies, Map<Right, Integer> priorities)
{
RuleState state = rule.getState();
if (state == UNDETERMINED) {
return;
}
if (rule.match(user)) {
resolveConflict(state, right, access, policies, USER_PRIORITY, priorities);
} else {
for (GroupSecurityReference group : groups) {
if (rule.match(group)) {
resolveConflict(state, right, access, policies, getPriority(group), priorities);
break;
}
}
}
}
/**
* Resolve conflicting rights within the current level in the document hierarchy.
*
* @param state The state to consider setting.
* @param right The right that is being concerned.
* @param access The accumulated access result.
* @param policies the current security policies.
* @param priority The priority to use for this particular right match.
* @param priorities A map of current priorities of each rights in the current accumulated access level.
*/
private void resolveConflict(RuleState state, Right right, XWikiSecurityAccess access, Policies policies,
int priority, Map<Right, Integer> priorities)
{
if (access.get(right) == UNDETERMINED) {
access.set(right, state);
priorities.put(right, priority);
return;
}
if (access.get(right) != state) {
if (priority > priorities.get(right)) {
access.set(right, state);
priorities.put(right, priority);
} else {
access.set(right, policies.getTieResolutionPolicy(right));
}
}
}
/**
* @param group A group identifier.
* @return the priority for the group.
*/
private int getPriority(GroupSecurityReference group)
{
if (group.getName().equals("XWikiAllGroup")) {
return ALL_GROUP_PRIORITY;
} else {
return ALL_GROUP_PRIORITY + 1;
}
}
}