All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.api.admin.AccessRequired Maven / Gradle / Ivy

There is a newer version: 8.0.0-JDK17-M7
Show newest version
/*
 * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.api.admin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.jvnet.hk2.config.ConfigBeanProxy;
import org.jvnet.hk2.config.ConfigModel;
import org.jvnet.hk2.config.Dom;

/**
 * Allows command developers to declare what resources are affected by the command and what actions must be authorized
 * on each to allow the command to execute.
 * 

* Use any or all of the following to control authorization: *

    *
  • Use the {@code @AccessRequired} annotation at the class level to declare a resource name and action to be * enforced; use {@code @AccessRequired.List} to declare more than one combination of resources and actions. *
  • Use the {@code @AccessRequired.To} annotation on a field that is a ConfigBean to declare one or more actions to * be enforced on the resource derived from that config bean. *
  • Have the command class implement {@code @AccessRequired.Authorizer} which prescribes the {@code isAuthorized} * method that will make authorization decisions internally, without help from the command framework. *
* The command processor will find all {@code @AccessRequired} annotations and subannotations and make sure all of them * pass before allowing the command to proceed. * * * @author tjquinn */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AccessRequired { /** * Name(s) of the resource(s) to which access should be checked. The name string can contain one or more tokens of the * form ${fieldName} where "fieldName" is a field on the same command class (or a superclass) that contains a non-null * value. If the referenced field is a ConfigBean then at runtime the resource name of that ConfigBean replaces the * token. Otherwise the field's {@code toString()} return value replaces the token. */ public String[] resource(); /** * One or more actions to authorize against the named resource. */ public String[] action(); /** * Declares multiple class-level {@code @AccessRequired} authorization steps, typically each identifying different * resources and/or different actions */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface List { public AccessRequired[] value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Delegate { public Class value(); } /** * Declares access control on an existing, non-null {@code ConfigBean}. The system gets the name of the resource from * the {@code ConfigBean} itself and authorizes the specified actions using that resource. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface To { /** * Action(s) to be authorized on the ConfigBean * * @return */ public String[] value(); } /** * Declares access control for creating a new child {@code ConfigBean} in a collection on an existing * {@code ConfigBean}. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface NewChild { /** * Name of the collection on the owner that will contain the new child. */ public String collection() default ""; /** * Type of the new {@code ConfigBean} to be created. */ public Class type(); /** * Action(s) to be authorized, defaulted to "create." */ public String[] action() default "create"; /** * Declares multiple authorization checks for creating the same single new {@code ConfigBean}. */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface List { public NewChild[] value(); } } /** * Represents an authorization check: a resource and an action to be authorized on that resource. *

* Note that the resource can be identified in one of several ways: *

    *
  • with the resource name *
  • with a resource {@code ConfigBean} *
  • with a {@code ConfigBean} parent and a child type *
*

* Secure admin submits each {@code AccessCheck} to the authorization service separately and records the result as * {@link #isSuccessful} which can be retrieved by commands that prepare their own access checks. *

* A command which prepares its own access checks can also indicate if a failure of the access check should or should * not be fatal to the overall authorization operation. This is useful, for example, in attempting to list all tenants. * The command could prepare an {@code AccessCheck} for each tenant of interest, marking each as non-fatal. Because * secure admin records the success of each access check, the "list tenant {@code execute} command can check each of its * custom {@code AccessCheck}s and report on only those accounts whose access checks succeeded. *

* Often, commands which prepare their own access checks need to associate an object of some type with the access check. * As a convenience such classes can optionally pass the related object to one of the constructors which accepts it and * then retrieve it later. This helps avoid having to extend AccessCheck as a private inner class in a command so as to * link a given AccessCheck with a given object of interest in the command. * */ public class AccessCheck { /** * Implements a collection of the related objects in an AccessCheck collection. *

* Some classes which use collections of AccessChecks need to iterate through the related objects associated with each * of the AccessChecks. Those classes can then use the AccessCheck.accessibleRelatedObjects() method, passing the typed * collection of access checks. Normally the related object collection will contain the related objects for only those * access checks which were successful but the caller can ask for all related objects if needed. * * @param the type of related object stored in the access checks in the collection */ private static class RelatedObjectCollection extends AbstractCollection { private final Collection> accessChecks; private final boolean accessibleOnly; private RelatedObjectCollection(final Collection> accessChecks, final boolean accessibleOnly) { this.accessChecks = accessChecks; this.accessibleOnly = accessibleOnly; } @Override public Iterator iterator() { return new Iterator<>() { private final Iterator> baseIt = accessChecks.iterator(); /* will be null if there is no matching next access check */ private AccessCheck nextAccessCheck = chooseNext(); private AccessCheck chooseNext() { while (baseIt.hasNext()) { final AccessCheck next = baseIt.next(); if (!accessibleOnly || next.isSuccessful) { return next; } } return null; } @Override public boolean hasNext() { return nextAccessCheck != null; } @Override public U next() { if (nextAccessCheck == null) { throw new NoSuchElementException(); } final U result = nextAccessCheck.relatedObject; nextAccessCheck = chooseNext(); return result; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } @Override public int size() { int count = 0; for (AccessCheck accessCheck : accessChecks) { if (!accessibleOnly || accessCheck.isSuccessful()) { count++; } } return count; } } /** * Returns an immutable collection of the related objects associated with all of the specified access checks provided * that the associated access check was successful. * * @param * @param accessChecks the collection of access checks (with related objects) to process * @return */ public static Collection relatedObjects(final Collection> accessChecks) { return relatedObjects(accessChecks, true); } /** * Returns an immutable collection of the related objects associated with all of the specified access checks, regardless * of whether the access was granted for each. * * @param * @param accessChecks the collection of access checks (with related objects) to process * @param successfulOnly whether to return the related objects for only the successful checks or for all checks * @return */ public static Collection relatedObjects(final Collection> accessChecks, final boolean successfulOnly) { return new RelatedObjectCollection<>(accessChecks, successfulOnly); } private final String resourceName; private final String action; private final String note; private final Class childType; private final ConfigBeanProxy parent; private final String childName; private final ConfigBeanProxy resource; private final boolean isFailureFatal; private boolean isSuccessful = false; private final T relatedObject; /** * Creates a new AccessCheck object linked with a given related object that is of interest to the caller (typically a * command). * * @param relatedObject the related object to which this AccessCheck is linked * @param resourceName the resource being acted upon * @param action the action performed on the resource * @param note a note related to this resource/action pair * @param isFailureFinal if a failure of this AccessCheck should cause the entire authorization to fail */ public AccessCheck(final T relatedObject, final String resourceName, final String action, final String note, final boolean isFailureFinal) { this.relatedObject = relatedObject; this.resourceName = resourceName; this.action = action; this.note = note; childType = null; parent = null; this.isFailureFatal = isFailureFinal; resource = null; childName = null; } /** * Creates a new {@code AccessCheck}. * * @param resourceName the resource to be checked * @param action the action on the resource * @param note descriptive note about the access check; used during logging * @param isFailureFinal whether a failure of this access check should cause the entire authorization to fail */ public AccessCheck(final String resourceName, final String action, final String note, final boolean isFailureFinal) { this(null, resourceName, action, note, isFailureFinal); } /** * Creates a new {@code AccessCheck}. * * @param resourceName the name of the resource to be checked * @param action the action on the resource * @param note descriptive note about the access check; used during logging */ public AccessCheck(final String resourceName, final String action, final String note) { this(resourceName, action, note, true /* isFailureFinal */); } /** * Creates a new {@code AccessCheck}. * * @param resourceName the name of the resource to be checked * @param action the action on the resource * @param isFailureFinal whether a failure of this access check should force a failure of the entire authorization * operation */ public AccessCheck(final String resourceName, final String action, final boolean isFailureFinal) { this(resourceName, action, "", isFailureFinal); } /** * Creates a new {@code AccessCheck} with the specified related object. * * @param relatedObject an object the commmand wants to associate with this AccessCheck * @param resourceName the resource to be checked * @param action the action on the resource * @param isFailureFinal whether a failure of this access check should force a failure of the entire authorization */ public AccessCheck(final T relatedObject, final String resourceName, final String action, final boolean isFailureFinal) { this(relatedObject, resourceName, action, "", isFailureFinal); } /** * Creates a new {@code AccessCheck}. * * @param resource the config bean that is the resource to check * @param action the action on the resource * @param isFailureFatal whether a failure of this access check should force a failure of the entire authorization * operation */ public AccessCheck(final ConfigBeanProxy resource, final String action, final boolean isFailureFatal) { this.resourceName = null; this.resource = resource; this.action = action; this.note = null; childType = null; parent = resource.getParent(); this.isFailureFatal = isFailureFatal; childName = null; relatedObject = null; } /** * Creates a new {@code AccessCheck}. * * @param resourceName the resource to be checked * @param action the action on the resource */ public AccessCheck(final String resourceName, final String action) { this(resourceName, action, "", true /* isFailureFinal */); } /** * Creates a new {@code AccessCheck}. * * @param parent the config bean of the parent resource to which a child is to be added * @param childType the type of the child to be added * @param action the action on the resource (typically "create") * @param note descriptive note about the access check; used during logging * @param isFailureFinal whether a failure of this access check should force a failure of the entire authorization * operation */ public AccessCheck(final ConfigBeanProxy parent, final Class childType, final String action, final String note, final boolean isFailureFinal) { this(parent, childType, null /* childName */, action, note, isFailureFinal); } public AccessCheck(final ConfigBeanProxy parent, final Class childType, final String childName, final String action, final String note, final boolean isFailureFinal) { this.parent = parent; this.childType = childType; this.action = action; this.note = note; this.resourceName = null; this.isFailureFatal = isFailureFinal; resource = null; this.childName = childName; relatedObject = null; } /** * Creates a new {@code AccessCheck}. * * @param parent the config bean of the parent resource to which a child is to be added * @param childType the type of the child to be added * @param action the action on the resource (typically "create") * @param isFailureFinal whether a failure of this access check should force a failure of the entire authorization * operation */ public AccessCheck(final ConfigBeanProxy parent, final Class childType, final String action, final boolean isFailureFinal) { this(parent, childType, action, "", isFailureFinal); } /** * Creates a new {@code AccessCheck}. * * @param parent the config bean of the parent resource to which a child is to be added * @param childType the type of the child to be added * @param action the action on the resource (typically "create") */ public AccessCheck(final ConfigBeanProxy parent, final Class childType, final String action) { this(parent, childType, action, true); } /** * Creates a new {@code AccessCheck} (typically for an existing target child) * * @param parent the parent of the config bean to be accessed * @param childType the type of the child config bean * @param childName the name of the child config bean * @param action the action on the resource */ public AccessCheck(final ConfigBeanProxy parent, final Class childType, final String childName, final String action) { this(Util.resourceNameFromConfigBeanTypeAndName(parent, childType, childName), action); } public T relatedObject() { return relatedObject; } /** * Returns the resource name, if any was set when the access check was created. * * @return */ public String resourceName() { if (resource != null) { return Util.resourceNameFromConfigBeanProxy(resource); } if (parent != null) { if (childName == null) { return Util.resourceNameFromConfigBeanType(parent, null, childType); } else { return Util.resourceNameFromConfigBeanTypeAndName(parent, childType, childName); } } return resourceName; } /** * Returns the action for the access check. * * @return */ public String action() { return action; } /** * Returns the type of the child to be added as part of a create-style operation, as set when the access check was * created. * * @return */ public Class childType() { return childType; } /** * Returns the parent config bean to which a child was to be added. * * @return */ public ConfigBeanProxy parent() { return parent; } /** * Returns the note associated with the access check. * * @return */ public String note() { return note; } /** * Returns the config bean to be acted upon * * @return */ public ConfigBeanProxy resource() { return resource; } /** * Returns whether a failure of this access check would automatically trigger a failure of the entire authorization * operation of which it is a part. * * @return */ public boolean isFailureFinal() { return isFailureFatal; } /** * Invoked by secure admin to record the result of performing the access check; command developers should not * typically use this method themselves. * * @param passed */ public void setSuccessful(final boolean passed) { isSuccessful = passed; } /** * Returns whether the access check succeeded. * * @return */ public boolean isSuccessful() { return isSuccessful; } /** * Formats the access check as a human-friendly string. * * @return */ @Override public String toString() { return new StringBuilder("AccessCheck ").append(resourceName()).append("=").append(action).append(", isSuccessful=").append(isSuccessful) .append(", isFailureFatal=").append(isFailureFatal).append("//").append(note).toString(); } } /** * Utility methods used both from AccessCheck and from CommandSecurityChecker. */ public static class Util { public static String resourceNameFromDom(Dom d) { Dom lastDom = null; final StringBuilder path = new StringBuilder(); while (d != null) { if (path.length() > 0) { path.insert(0, '/'); } final ConfigModel m = d.model; lastDom = d; final String key = d.getKey(); final String pathSegment = m.getTagName() + (key == null ? "" : "/" + key); path.insert(0, pathSegment); d = d.parent(); } if ((lastDom != null) && (lastDom.getKey() != null)) { path.insert(0, pluralize(lastDom.model.getTagName()) + '/'); } return path.toString(); } public static String resourceNameFromConfigBeanProxy(ConfigBeanProxy b) { return b == null ? null : resourceNameFromDom(Dom.unwrap(b)); } public static String resourceNameFromConfigBeanType(final ConfigBeanProxy parent, final String collectionName, final Class childType) { return parent == null ? null : resourceNameFromConfigBeanType(Dom.unwrap(parent), collectionName, childType); } public static String resourceNameFromConfigBeanType(final Dom parent, String collectionName, final Class childType) { final StringBuilder sb = new StringBuilder(resourceNameFromDom(parent)).append('/'); final String tagName = parent.document.buildModel(childType).getTagName(); if (collectionName != null) { if (collectionName.isEmpty()) { collectionName = pluralize(tagName); } sb.append(collectionName).append('/'); } sb.append(tagName); return sb.toString(); } static String resourceNameFromConfigBeanTypeAndName(final ConfigBeanProxy parent, final Class childType, final String childName) { return resourceNameFromConfigBeanType(parent, null, childType) + (childName != null && !childName.isEmpty() ? "/" + childName : ""); } private static String pluralize(final String s) { final char lastChar = s.charAt(s.length() - 1); if (lastChar == 's' || lastChar == 'S' || lastChar == 'x' || lastChar == 'X') { return s + "es"; } else { return s + "s"; } } } }