/*
* 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.io.Serializable;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.opensolaris.opengrok.configuration.Group;
import org.opensolaris.opengrok.configuration.Nameable;
import org.opensolaris.opengrok.configuration.Project;
import org.opensolaris.opengrok.logger.LoggerFactory;
/**
* This class covers authorization entities used in opengrok.
*
* Currently there are two:
* <ul>
* <li>stack of plugins</li>
* <li>plugin</li>
* </ul>
*
* The purpose is to extract common member variables and methods into an class,
* namely:
* <ul>
* <li>name</li>
* <li>role - sufficient/required/requisite</li>
* <li>state - working/failed</li>
* <li>setup - from configuration</li>
* </ul>
* and let the subclasses implement the important abstract methods.
*
* This class is intended to be read from a configuration.
*
* @author Krystof Tulinger
*/
public abstract class AuthorizationEntity implements Nameable, Serializable, Cloneable {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationEntity.class);
/**
* Predicate specialized for the the plugin decisions. The caller should
* implement the <code>decision</code> method. Returning true if the plugin
* allows the action or false when the plugin forbids the action.
*/
public static abstract class PluginDecisionPredicate implements Predicate<IAuthorizationPlugin> {
@Override
public boolean test(IAuthorizationPlugin t) {
return decision(t);
}
/**
* Perform the authorization check for this plugin.
*
* @param t the plugin
* @return true if plugin allows the action; false otherwise
*/
public abstract boolean decision(IAuthorizationPlugin t);
}
/**
* Predicate specialized for the the entity skipping decisions. The caller
* should implement the <code>shouldSkip</code> method. Returning true if
* the entity should be skipped for this action and false if the entity
* should be used.
*/
public static abstract class PluginSkippingPredicate implements Predicate<AuthorizationEntity> {
@Override
public boolean test(AuthorizationEntity t) {
return shouldSkip(t);
}
/**
* Decide if the entity should be skipped in this step of authorization.
*
* @param t the entity
* @return true if skipped (authorization decision will not be affected
* by this entity) or false if it should be used (authorization decision
* will be affected by this entity)
*/
public abstract boolean shouldSkip(AuthorizationEntity t);
}
private static final long serialVersionUID = 1L;
/**
* One of "required", "requisite", "sufficient".
*/
protected AuthControlFlag flag;
protected String name;
protected Map<String, Object> setup = new TreeMap<>();
private Set<String> forProjects = new TreeSet<>();
private Set<String> forGroups = new TreeSet<>();
protected transient boolean working = true;
public AuthorizationEntity() {
}
/**
* Copy constructor for the entity:
* <ul>
* <li>copy flag</li>
* <li>copy name</li>
* <li>deep copy of the setup</li>
* <li>copy the working attribute</li>
* </ul>
*
* @param x the entity to be copied
*/
public AuthorizationEntity(AuthorizationEntity x) {
flag = x.flag;
name = x.name;
setup = new TreeMap<>(x.setup);
working = x.working;
forGroups = new TreeSet<>(x.forGroups);
forProjects = new TreeSet<>(x.forProjects);
}
public AuthorizationEntity(AuthControlFlag flag, String name) {
this.flag = flag;
this.name = name;
}
/**
* Load this entity with given parameters.
*
* @param parameters given parameters passed to the plugin's load method
*
* @see IAuthorizationPlugin#load(java.util.Map)
*/
abstract public void load(Map<String, Object> parameters);
/**
* Unload this entity.
*
* @see IAuthorizationPlugin#unload()
*/
abstract public void unload();
/**
* Test the given entity if it should be allowed with this authorization
* check.
*
* @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
* @param skippingPredicate predicate returning true if this authorization
* entity should be omitted from the authorization process
* @return true if successful; false otherwise
*/
abstract public boolean isAllowed(Nameable entity,
PluginDecisionPredicate pluginPredicate,
PluginSkippingPredicate skippingPredicate);
/**
* Set the plugin to all classes which requires this class in the
* configuration. This creates a new instance of the plugin for each class
* which needs it.
*
* @param plugin the new instance of a plugin
* @return true if there is such case; false otherwise
*/
abstract public boolean setPlugin(IAuthorizationPlugin plugin);
/**
* Perform a deep copy of the entity.
*
* @return the new instance of this entity
*/
@Override
abstract public AuthorizationEntity clone();
/**
* Get the value of flag
*
* @return the value of flag
*/
public AuthControlFlag getFlag() {
return flag;
}
/**
* Set the value of flag
*
* @param flag new value of flag
*/
public void setFlag(AuthControlFlag flag) {
this.flag = flag;
}
/**
* Set the value of flag
*
* @param flag new value of flag
*/
public void setFlag(String flag) {
this.flag = AuthControlFlag.get(flag);
}
/**
* Get the value of name
*
* @return the value of name
*/
@Override
public String getName() {
return name;
}
/**
* Set the value of name
*
* @param name new value of name
*/
@Override
public void setName(String name) {
this.name = name;
}
/**
* Get the value of setup
*
* @return the value of setup
*/
public Map<String, Object> getSetup() {
return setup;
}
/**
* Set the value of setup
*
* @param setup new value of setup
*/
public void setSetup(Map<String, Object> setup) {
this.setup = setup;
}
/**
* Get the value of forProjects
*
* @return the value of forProjects
*/
public Set<String> forProjects() {
return getForProjects();
}
/**
* Get the value of forProjects
*
* @return the value of forProjects
*/
public Set<String> getForProjects() {
return forProjects;
}
/**
* Set the value of forProjects
*
* @param forProjects new value of forProjects
*/
public void setForProjects(Set<String> forProjects) {
this.forProjects = forProjects;
}
/**
* Set the value of forProjects
*
* @param project add this project into the set
*/
public void setForProjects(String project) {
this.forProjects.add(project);
}
/**
* Set the value of forProjects
*
* @param projects add all projects in this array into the set
*
* @see #setForProjects(java.lang.String)
*/
public void setForProjects(String[] projects) {
for (String project : projects) {
setForProjects(project);
}
}
/**
* Get the value of forGroups
*
* @return the value of forGroups
*/
public Set<String> forGroups() {
return getForGroups();
}
/**
* Get the value of forGroups
*
* @return the value of forGroups
*/
public Set<String> getForGroups() {
return forGroups;
}
/**
* Set the value of forGroups
*
* @param forGroups new value of forGroups
*/
public void setForGroups(Set<String> forGroups) {
this.forGroups = forGroups;
}
/**
* Set the value of forGroups
*
* @param group add this group into the set
*/
public void setForGroups(String group) {
this.forGroups.add(group);
}
/**
* Set the value of forGroups
*
* @param groups add all groups in this array into the set
*
* @see #setForGroups(java.lang.String)
*/
public void setForGroups(String[] groups) {
for (String group : groups) {
setForGroups(group);
}
}
/**
* Discover all targeted groups and projects for every group given by
* {@link #forGroups()}.
*
* <ul>
* <li>add to the {@link #forGroups()} all groups which are descendant
* groups to the group</li>
* <li>add to the {@link #forGroups()} all groups which are parent groups to
* the group</li>
* <li>add to the {@link #forProjects()} all projects and repositories which
* are in the descendant groups or in the group itself</li>
* <li>issue a warning for non-existent groups</li>
* <li>issue a warning for non-existent projects</li>
* </ul>
*/
protected void processTargetGroupsAndProjects() {
Set<String> groups = new TreeSet<>();
for (String x : forGroups()) {
/**
* Full group discovery takes place here. All projects/repositories
* in the group are added into "forProjects" and all subgroups
* (including projects/repositories) and parent groups (excluding
* the projects/repositories) are added into "forGroups".
*
* If the group does not exist then a warning is issued.
*/
Group g;
if ((g = Group.getByName(x)) != null) {
forProjects().addAll(g.getAllProjects().stream().map((t) -> t.getName()).collect(Collectors.toSet()));
groups.addAll(g.getRelatedGroups().stream().map((t) -> t.getName()).collect(Collectors.toSet()));
groups.add(x);
} else {
LOGGER.log(Level.WARNING, "Configured group \"{0}\" in forGroups section"
+ " for name \"{1}\" does not exist",
new Object[]{x, getName()});
}
}
setForGroups(groups);
forProjects().removeIf((t) -> {
/**
* Check the existence of the projects and issue a warning if there
* is no such project.
*/
Project p;
if ((p = Project.getByName(t)) == null) {
LOGGER.log(Level.WARNING, "Configured project \"{0}\" in forProjects"
+ " section for name \"{1}\" does not exist",
new Object[]{t, getName()});
return true;
}
return false;
});
}
/**
* Check if the plugin exists and has not failed while loading.
*
* @return true if working, false otherwise
*/
public boolean isWorking() {
return working;
}
/**
* Mark this entity as working.
*/
public synchronized void setWorking() {
working = true;
}
/**
* Check if this plugin has failed during loading or is missing.
*
* This method has the same effect as !{@link isWorking()}.
*
* @return true if failed, true otherwise
* @see #isWorking()
*/
public boolean isFailed() {
return !isWorking();
}
/**
* Set this plugin as failed. This plugin will no more call the underlying
* plugin isAllowed methods.
*
* @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Group)
* @see IAuthorizationPlugin#isAllowed(HttpServletRequest, Project)
*/
public synchronized void setFailed() {
working = false;
}
/**
* Check if this plugin is marked as required.
*
* @return true if is required; false otherwise
*/
public boolean isRequired() {
return getFlag().isRequired();
}
/**
* Check if this plugin is marked as sufficient.
*
* @return true if is sufficient; false otherwise
*/
public boolean isSufficient() {
return getFlag().isSufficient();
}
/**
* Check if this plugin is marked as requisite.
*
* @return true if is requisite; false otherwise
*/
public boolean isRequisite() {
return getFlag().isRequisite();
}
}