/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. */ package org.opensolaris.opengrok.authorization; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import org.opensolaris.opengrok.configuration.Nameable; import org.opensolaris.opengrok.logger.LoggerFactory; /** * This is a subclass of {@link AuthorizationEntity} implementing the methods to * be able to contain and making decision for: * <ul> * <li>other stacks</li> * <li>plugins</li> * </ul> * * @author Krystof Tulinger */ public class AuthorizationStack extends AuthorizationEntity { private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationStack.class); private List<AuthorizationEntity> stack = new ArrayList<>(); public AuthorizationStack() { } /** * Copy constructor from another stack * <ul> * <li>copy the superclass {@link AuthorizationEntity}</li> * <li>perform a deep copy of the contained stack (using * {@link AuthorizationEntity#clone()}</li> * </ul> * * @param x the stack to be copied */ public AuthorizationStack(AuthorizationStack x) { super(x); stack = new ArrayList<>(x.stack.size()); for (AuthorizationEntity e : x.getStack()) { stack.add(e.clone()); } } public AuthorizationStack(AuthControlFlag flag, String name) { super(flag, name); } /** * Get the value of stack * * @return the current stack */ public List<AuthorizationEntity> getStack() { return stack; } /** * Set the value of stack * * @param s the new stack */ public void setStack(List<AuthorizationEntity> s) { this.stack = s; } /** * Add a new authorization entity into this stack. * * @param s new entity */ public void add(AuthorizationEntity s) { this.stack.add(s); } /** * Remove the given authorization entity from this stack. * * @param s the entity to remove */ public void remove(AuthorizationEntity s) { s.unload(); this.stack.remove(s); } /** * Load all authorization entities in this stack. * * <p> * If the method is unable to load any of the entities contained in this * stack then this stack is marked as failed. Note that it does not affect * the authorization decision made by this stack. * </p> * * @param parameters parameters given in the configuration * * @see IAuthorizationPlugin#load(java.util.Map) */ @Override public void load(Map<String, Object> parameters) { Map<String, Object> s = new TreeMap<>(); s.putAll(parameters); s.putAll(getSetup()); LOGGER.log(Level.INFO, "[{0}] Stack \"{1}\" is loading.", new Object[]{ getFlag().toString().toUpperCase(), getName()}); // fill properly the "forGroups" and "forProjects" fields processTargetGroupsAndProjects(); setWorking(); int cnt = 0; for (AuthorizationEntity authEntity : getStack()) { authEntity.load(s); if (authEntity.isWorking()) { cnt++; } } if (getStack().size() > 0 && cnt < getStack().size()) { setFailed(); } LOGGER.log(Level.INFO, "[{0}] Stack \"{1}\" is {2}.", new Object[]{ getFlag().toString().toUpperCase(), getName(), isWorking() ? "ready" : "not fully ok"}); } /** * Unload all plugins contained in this stack. * * @see IAuthorizationPlugin#unload() */ @Override public void unload() { for (AuthorizationEntity plugin : getStack()) { plugin.unload(); } } /** * Test the given entity if it should be allowed with in this stack context * if and only if the stack is not marked as failed. * * @param entity the given entity - this is either group or project and is * passed just for the logging purposes. * @param pluginPredicate predicate returning true or false for the given * entity which determines if the authorization for such entity is * successful or failed for particular request and plugin * @param skippingPredicate predicate returning true if this authorization * entity should be omitted from the authorization process * @return true if successful; false otherwise */ @Override public boolean isAllowed(Nameable entity, PluginDecisionPredicate pluginPredicate, PluginSkippingPredicate skippingPredicate) { boolean overallDecision = true; LOGGER.log(Level.FINER, "Authorization for \"{0}\" in \"{1}\" [{2}]", new Object[]{entity.getName(), this.getName(), this.getFlag()}); if (skippingPredicate.shouldSkip(this)) { LOGGER.log(Level.FINER, "AuthEntity \"{0}\" [{1}] skipping testing of name \"{2}\"", new Object[]{this.getName(), this.getFlag(), entity.getName()}); } else { overallDecision = processStack(entity, pluginPredicate, skippingPredicate); } LOGGER.log(Level.FINER, "Authorization for \"{0}\" in \"{1}\" [{2}] => {3}", new Object[]{entity.getName(), this.getName(), this.getFlag(), overallDecision ? "true" : "false"}); return overallDecision; } /** * Process the stack. * * @param entity the given entity * @param pluginPredicate predicate returning true or false for the given * entity which determines if the authorization for such entity is * successful or failed for particular request and plugin * @param skippingPredicate predicate returning true if this authorization * entity should be omitted from the authorization process * @return true if entity is allowed; false otherwise */ protected boolean processStack(Nameable entity, PluginDecisionPredicate pluginPredicate, PluginSkippingPredicate skippingPredicate) { boolean overallDecision = true; for (AuthorizationEntity authEntity : getStack()) { if (skippingPredicate.shouldSkip(authEntity)) { LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] skipping testing of name \"{2}\"", new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName()}); continue; } // run the plugin's test method try { LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\"", new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName()}); boolean pluginDecision = authEntity.isAllowed(entity, pluginPredicate, skippingPredicate); LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\" => {3}", new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName(), pluginDecision ? "true" : "false"}); if (!pluginDecision && authEntity.isRequired()) { // required sets a failure but still invokes all other plugins overallDecision = false; continue; } else if (!pluginDecision && authEntity.isRequisite()) { // requisite sets a failure and immediately returns the failure overallDecision = false; break; } else if (overallDecision && pluginDecision && authEntity.isSufficient()) { // sufficient immediately returns the success overallDecision = true; break; } } catch (Throwable ex) { LOGGER.log(Level.WARNING, String.format("AuthEntity \"%s\" has failed the testing of \"%s\" with an exception.", authEntity.getName(), entity.getName()), ex); LOGGER.log(Level.FINEST, "AuthEntity \"{0}\" [{1}] testing a name \"{2}\" => {3}", new Object[]{authEntity.getName(), authEntity.getFlag(), entity.getName(), "false (failed)"}); // set the return value to false for this faulty plugin if (!authEntity.isSufficient()) { overallDecision = false; } // requisite plugin may immediately return the failure if (authEntity.isRequisite()) { break; } } } return overallDecision; } /** * Set the plugin to all classes in this stack which requires this class in * the configuration. This creates a new instance of the plugin for each * class which needs it. * * <p> * This is where the loaded plugin classes get to be a part of the * authorization process. When the {@link AuthorizationPlugin} does not get * its {@link IAuthorizationPlugin} it is marked as failed and returns false * to all authorization decisions. * </p> * * @param plugin the new instance of a plugin * @return true if there is such case; false otherwise */ @Override public boolean setPlugin(IAuthorizationPlugin plugin) { boolean ret = false; for (AuthorizationEntity p : getStack()) { ret = p.setPlugin(plugin) || ret; } return ret; } /** * Clone the stack: * <ul> * <li>copy the superclass {@link AuthorizationEntity}</li> * <li>perform a deep copy of the contained stack</li> * </ul> * * @return new instance of {@link AuthorizationStack} */ @Override public AuthorizationStack clone() { return new AuthorizationStack(this); } }