/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * 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.jboss.as.controller.operations.global; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_TYPE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALL_SERVICES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CHILDREN; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEFAULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXCEPTIONS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXECUTE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.JVM; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MODEL_DESCRIPTION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NO_SERVICES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESOURCE_SERVICES; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESTART_REQUIRED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STORAGE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE; import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.INCLUDE_ALIASES; import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.LOCALE; import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.PROXIES; import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.RECURSIVE; import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.RECURSIVE_DEPTH; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationDefinition; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ProcessType; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.UnauthorizedException; import org.jboss.as.controller.access.Action.ActionEffect; import org.jboss.as.controller.access.AuthorizationResult; import org.jboss.as.controller.access.AuthorizationResult.Decision; import org.jboss.as.controller.access.ResourceAuthorization; import org.jboss.as.controller.descriptions.DescriptionProvider; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.NonResolvingResourceDescriptionResolver; import org.jboss.as.controller.descriptions.common.ControllerResolver; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.operations.validation.EnumValidator; import org.jboss.as.controller.registry.AliasEntry; import org.jboss.as.controller.registry.AliasStepHandler; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.AttributeAccess.Storage; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.NotificationEntry; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; /** * {@link org.jboss.as.controller.OperationStepHandler} querying the complete type description of a given model node. * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> * @author Brian Stansberry (c) 2012 Red Hat Inc. */ public class ReadResourceDescriptionHandler extends GlobalOperationHandlers.AbstractMultiTargetHandler { private static final SimpleAttributeDefinition INHERITED = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.INHERITED, ModelType.BOOLEAN) .setAllowNull(true) .setDefaultValue(new ModelNode(true)) .build(); private static final SimpleAttributeDefinition OPERATIONS = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.OPERATIONS, ModelType.BOOLEAN) .setAllowNull(true) .setDefaultValue(new ModelNode(false)) .build(); private static final SimpleAttributeDefinition NOTIFICATIONS = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.NOTIFICATIONS, ModelType.BOOLEAN) .setAllowNull(true) .setDefaultValue(new ModelNode(false)) .build(); private static final SimpleAttributeDefinition ACCESS_CONTROL = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.ACCESS_CONTROL, ModelType.STRING) .setAllowNull(true) .setDefaultValue(new ModelNode(AccessControl.NONE.toString())) .setValidator(EnumValidator.create(AccessControl.class, true, AccessControl.NONE, AccessControl.COMBINED_DESCRIPTIONS, AccessControl.TRIM_DESCRIPTONS)) .build(); static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(READ_RESOURCE_DESCRIPTION_OPERATION, ControllerResolver.getResolver("global")) .setParameters(OPERATIONS, NOTIFICATIONS, INHERITED, RECURSIVE, RECURSIVE_DEPTH, PROXIES, INCLUDE_ALIASES, ACCESS_CONTROL, LOCALE) .setReadOnly() .setRuntimeOnly() .setReplyType(ModelType.OBJECT) .build(); static final OperationStepHandler INSTANCE = new ReadResourceDescriptionHandler(); //Placeholder for NoSuchResourceExceptions coming from proxies to remove the child in ReadResourceDescriptionAssemblyHandler private static final ModelNode PROXY_NO_SUCH_RESOURCE; static { //Create something non-used since we cannot ModelNode none = new ModelNode(); none.get("no-such-resource").set("no$such$resource"); none.protect(); PROXY_NO_SUCH_RESOURCE = none; } private ReadResourceDescriptionHandler() { super(true); } ReadResourceDescriptionAccessControlContext getAccessControlContext() { return null; } @Override void doExecute(OperationContext context, ModelNode operation, FilteredData filteredData, boolean ignoreMissingResource) throws OperationFailedException { final PathAddress address = context.getCurrentAddress(); ReadResourceDescriptionAccessControlContext accessControlContext = getAccessControlContext() == null ? new ReadResourceDescriptionAccessControlContext(address, null) : getAccessControlContext(); doExecute(context, operation, accessControlContext); } void doExecute(OperationContext context, ModelNode operation, ReadResourceDescriptionAccessControlContext accessControlContext) throws OperationFailedException { if (accessControlContext.parentAddresses == null) { doExecuteInternal(context, operation, accessControlContext); } else { try { doExecuteInternal(context, operation, accessControlContext); } catch (Resource.NoSuchResourceException | UnauthorizedException nsre) { context.getResult().set(new ModelNode()); } } } private void doExecuteInternal(final OperationContext context, final ModelNode operation, final ReadResourceDescriptionAccessControlContext accessControlContext) throws OperationFailedException { for (AttributeDefinition def : DEFINITION.getParameters()) { def.validateOperation(operation); } final String opName = operation.require(OP).asString(); PathAddress opAddr = PathAddress.pathAddress(operation.get(OP_ADDR)); // WFCORE-76 final boolean recursive = GlobalOperationHandlers.getRecursive(context, operation); final boolean proxies = PROXIES.resolveModelAttribute(context, operation).asBoolean(); final boolean ops = OPERATIONS.resolveModelAttribute(context, operation).asBoolean(); final boolean nots = NOTIFICATIONS.resolveModelAttribute(context, operation).asBoolean(); final boolean aliases = INCLUDE_ALIASES.resolveModelAttribute(context, operation).asBoolean(); final boolean inherited = INHERITED.resolveModelAttribute(context, operation).asBoolean(); final AccessControl accessControl = AccessControl.forName(ACCESS_CONTROL.resolveModelAttribute(context, operation).asString()); final ImmutableManagementResourceRegistration registry = getResourceRegistrationCheckForAlias(context, opAddr, accessControlContext); final DescriptionProvider descriptionProvider = registry.getModelDescription(PathAddress.EMPTY_ADDRESS); final Locale locale = GlobalOperationHandlers.getLocale(context, operation); final ModelNode nodeDescription = descriptionProvider.getModelDescription(locale); final Map<String, ModelNode> operations = ops ? new HashMap<String, ModelNode>() : null; final Map<String, ModelNode> notifications = nots ? new HashMap<String, ModelNode>() : null; final Map<PathElement, ModelNode> childResources = recursive ? new HashMap<PathElement, ModelNode>() : Collections.<PathElement, ModelNode>emptyMap(); if (accessControl != AccessControl.NONE) { accessControlContext.initLocalResourceAddresses(context, opAddr); } // We're going to add a bunch of steps that should immediately follow this one. We are going to add them // in reverse order of how they should execute, as that is the way adding a Stage.IMMEDIATE step works // Last to execute is the handler that assembles the overall response from the pieces created by all the other steps final ReadResourceDescriptionAssemblyHandler assemblyHandler = new ReadResourceDescriptionAssemblyHandler(nodeDescription, operations, notifications, childResources, accessControlContext, accessControl); context.addStep(assemblyHandler, OperationContext.Stage.MODEL, true); //Let's filter the children if (!aliases && nodeDescription.hasDefined(CHILDREN)) { for (Property child : nodeDescription.get(CHILDREN).asPropertyList()) { String key = child.getName(); if (isGlobalAlias(registry, child)) { nodeDescription.get(CHILDREN).remove(key); } } } if (ops) { for (final Map.Entry<String, OperationEntry> entry : registry.getOperationDescriptions(PathAddress.EMPTY_ADDRESS, inherited).entrySet()) { if (entry.getValue().getType() == OperationEntry.EntryType.PUBLIC) { if (context.getProcessType() != ProcessType.DOMAIN_SERVER || entry.getValue().getFlags().contains(OperationEntry.Flag.RUNTIME_ONLY)) { ReadOperationDescriptionHandler.DescribedOp describedOp = new ReadOperationDescriptionHandler.DescribedOp(entry.getValue(), locale); operations.put(entry.getKey(), describedOp.getDescription()); } } } } if (nots) { for (final Map.Entry<String, NotificationEntry> entry : registry.getNotificationDescriptions(PathAddress.EMPTY_ADDRESS, inherited).entrySet()) { final DescriptionProvider provider = entry.getValue().getDescriptionProvider(); notifications.put(entry.getKey(), provider.getModelDescription(locale)); } } if (nodeDescription.hasDefined(ATTRIBUTES)) { for (final String attr : nodeDescription.require(ATTRIBUTES).keys()) { final AttributeAccess access = registry.getAttributeAccess(PathAddress.EMPTY_ADDRESS, attr); // If there is metadata for an attribute but no AttributeAccess, assume RO. Can't // be writable without a registered handler. This opens the possibility that out-of-date metadata // for attribute "foo" can lead to a read of non-existent-in-model "foo" with // an unexpected undefined value returned. But it removes the possibility of a // dev forgetting to call registry.registerReadOnlyAttribute("foo", null) resulting // in the valid attribute "foo" not being readable final AttributeAccess.AccessType accessType = access == null ? AttributeAccess.AccessType.READ_ONLY : access.getAccessType(); final AttributeAccess.Storage storage = access == null ? AttributeAccess.Storage.CONFIGURATION : access.getStorageType(); final ModelNode attrNode = nodeDescription.get(ATTRIBUTES, attr); //AS7-3085 - For a domain mode server show writable attributes as read-only String displayedAccessType = context.getProcessType() == ProcessType.DOMAIN_SERVER && storage == AttributeAccess.Storage.CONFIGURATION ? AttributeAccess.AccessType.READ_ONLY.toString() : accessType.toString(); attrNode.get(ACCESS_TYPE).set(displayedAccessType); attrNode.get(STORAGE).set(storage.toString()); if (accessType == AttributeAccess.AccessType.READ_WRITE) { Set<AttributeAccess.Flag> flags = access.getFlags(); if (flags.contains(AttributeAccess.Flag.RESTART_ALL_SERVICES)) { attrNode.get(RESTART_REQUIRED).set(ALL_SERVICES); } else if (flags.contains(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES)) { attrNode.get(RESTART_REQUIRED).set(RESOURCE_SERVICES); } else if (flags.contains(AttributeAccess.Flag.RESTART_JVM)) { attrNode.get(RESTART_REQUIRED).set(JVM); } else { attrNode.get(RESTART_REQUIRED).set(NO_SERVICES); } } } } if (accessControl != AccessControl.NONE) { accessControlContext.checkResourceAccess(context, registry, nodeDescription, operations); } if (recursive) { for (final PathElement element : registry.getChildAddresses(PathAddress.EMPTY_ADDRESS)) { PathAddress relativeAddr = PathAddress.pathAddress(element); ImmutableManagementResourceRegistration childReg = registry.getSubModel(relativeAddr); boolean readChild = true; if (childReg.isRemote() && !proxies) { readChild = false; } if (childReg.isAlias() && !aliases) { readChild = false; } if (readChild) { final ModelNode rrOp = operation.clone(); final PathAddress address; try { address = PathAddress.pathAddress(opAddr, element); } catch (Exception e) { continue; } rrOp.get(OP_ADDR).set(address.toModelNode()); // WFCORE-76 GlobalOperationHandlers.setNextRecursive(context, operation, rrOp); final ModelNode rrRsp = new ModelNode(); childResources.put(element, rrRsp); final OperationStepHandler handler = getRecursiveStepHandler(childReg, opName, accessControlContext, address); context.addStep(rrRsp, rrOp, handler, OperationContext.Stage.MODEL, true); //Add a "child" => undefined nodeDescription.get(CHILDREN, element.getKey(), MODEL_DESCRIPTION, element.getValue()); } else if (childReg.isAlias() && !aliases) { if (isSingletonResource(registry, element.getKey())) { if (nodeDescription.get(CHILDREN).hasDefined(element.getKey())) { nodeDescription.get(CHILDREN).get(element.getKey()).remove(element.getValue()); } } } } } context.completeStep(new OperationContext.RollbackHandler() { @Override public void handleRollback(OperationContext context, ModelNode operation) { if (!context.hasFailureDescription()) { for (final ModelNode value : childResources.values()) { if (value.hasDefined(FAILURE_DESCRIPTION)) { context.getFailureDescription().set(value.get(FAILURE_DESCRIPTION)); break; } } } } }); } private boolean isSingletonResource(final ImmutableManagementResourceRegistration registry, final String key) { return registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(key))) == null; } private boolean isGlobalAlias(final ImmutableManagementResourceRegistration registry, final Property child) { if(isSingletonResource(registry, child.getName())) { Set<PathElement> childrenPath = registry.getChildAddresses(PathAddress.EMPTY_ADDRESS); boolean found = false; boolean alias = true; for(PathElement childPath : childrenPath) { if(childPath.getKey().equals(child.getName())) { found = true; ImmutableManagementResourceRegistration squatterRegistration = registry.getSubModel(PathAddress.pathAddress(childPath)); alias = alias && (squatterRegistration != null && squatterRegistration.isAlias()); } } if(found && alias) { return true; } ImmutableManagementResourceRegistration squatterRegistration = registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(child.getName(), child.getValue().asString()))); return squatterRegistration != null && squatterRegistration.isAlias(); } String key = child.getName(); ImmutableManagementResourceRegistration wildCardChildRegistration = registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(key))); boolean isAlias = wildCardChildRegistration.isAlias(); Set<String> registredNames = registry.getChildNames(PathAddress.pathAddress(PathElement.pathElement(key))); if (registredNames != null && !registredNames.isEmpty() && isAlias) { for (String value : registredNames) { ImmutableManagementResourceRegistration childRegistration = registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(key, value))); isAlias = isAlias && childRegistration != null && childRegistration.isAlias(); if(!isAlias) { return false; } } } return isAlias; } private OperationStepHandler getRecursiveStepHandler(ImmutableManagementResourceRegistration childReg, String opName, ReadResourceDescriptionAccessControlContext accessControlContext, PathAddress address) { OperationStepHandler overrideHandler = childReg.getOperationHandler(PathAddress.EMPTY_ADDRESS, opName); if (overrideHandler != null && (overrideHandler.getClass() == ReadResourceDescriptionHandler.class || overrideHandler.getClass() == AliasStepHandler.class)) { // not an override overrideHandler = null; } if (overrideHandler != null) { return new NestedReadResourceDescriptionHandler(overrideHandler); } else { return new NestedReadResourceDescriptionHandler(new ReadResourceDescriptionAccessControlContext(address, accessControlContext)); } } private ImmutableManagementResourceRegistration getResourceRegistrationCheckForAlias(OperationContext context, PathAddress opAddr, ReadResourceDescriptionAccessControlContext accessControlContext) { //The direct root registration is only needed if we are doing access-control=true final ImmutableManagementResourceRegistration root = context.getRootResourceRegistration(); final ImmutableManagementResourceRegistration registry = root.getSubModel(opAddr); AliasEntry aliasEntry = registry.getAliasEntry(); if (aliasEntry == null) { return registry; } //Get hold of the real registry if it was an alias PathAddress realAddress = aliasEntry.convertToTargetAddress(opAddr, AliasEntry.AliasContext.create(opAddr, context)); assert !realAddress.equals(opAddr) : "Alias was not translated"; return root.getSubModel(realAddress); } /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ static final class CheckResourceAccessHandler implements OperationStepHandler { static final OperationDefinition DEFAULT_DEFINITION = new SimpleOperationDefinitionBuilder(GlobalOperationHandlers.CHECK_DEFAULT_RESOURCE_ACCESS, new NonResolvingResourceDescriptionResolver()) .setPrivateEntry() .build(); static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(GlobalOperationHandlers.CHECK_RESOURCE_ACCESS, new NonResolvingResourceDescriptionResolver()) .setPrivateEntry() .build(); private final boolean runtimeResource; private final boolean defaultSetting; private final ModelNode accessControlResult; private final ModelNode nodeDescription; private final Map<String, ModelNode> operations; CheckResourceAccessHandler(boolean runtimeResource, boolean defaultSetting, ModelNode accessControlResult, ModelNode nodeDescription, Map<String, ModelNode> operations) { this.runtimeResource = runtimeResource; this.defaultSetting = defaultSetting; this.accessControlResult = accessControlResult; this.nodeDescription = nodeDescription; this.operations = operations; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { ModelNode result = new ModelNode(); boolean customDefaultCheck = operation.get(OP).asString().equals(GlobalOperationHandlers.CHECK_DEFAULT_RESOURCE_ACCESS); ResourceAuthorization authResp = context.authorizeResource(true, customDefaultCheck); if (authResp == null || authResp.getResourceResult(ActionEffect.ADDRESS).getDecision() == Decision.DENY) { if (!defaultSetting || authResp == null) { //We are not allowed to see the resource, so we don't set the accessControlResult, meaning that the ReadResourceAssemblyHandler will ignore it for this address } else { result.get(ActionEffect.ADDRESS.toString()).set(false); } } else { // if (!defaultSetting) { // result.get(ADDRESS).set(operation.get(OP_ADDR)); // } addResourceAuthorizationResults(result, authResp); ModelNode attributes = new ModelNode(); attributes.setEmptyObject(); if (result.get(READ).asBoolean()) { if (nodeDescription.hasDefined(ATTRIBUTES)) { for (Property attrProp : nodeDescription.require(ATTRIBUTES).asPropertyList()) { ModelNode attributeResult = new ModelNode(); Storage storage = Storage.valueOf(attrProp.getValue().get(STORAGE).asString().toUpperCase(Locale.ENGLISH)); addAttributeAuthorizationResults(attributeResult, attrProp.getName(), authResp, storage == Storage.RUNTIME); if (attributeResult.isDefined()) { attributes.get(attrProp.getName()).set(attributeResult); } } } result.get(ATTRIBUTES).set(attributes); if (operations != null) { ModelNode ops = new ModelNode(); ops.setEmptyObject(); PathAddress currentAddress = context.getCurrentAddress(); for (Map.Entry<String, ModelNode> entry : operations.entrySet()) { ModelNode operationToCheck = Util.createOperation(entry.getKey(), currentAddress); ModelNode operationResult = new ModelNode(); addOperationAuthorizationResult(context, operationResult, operationToCheck, entry.getKey()); ops.get(entry.getKey()).set(operationResult); } result.get(ModelDescriptionConstants.OPERATIONS).set(ops); } } } accessControlResult.set(result); } private void addResourceAuthorizationResults(ModelNode result, ResourceAuthorization authResp) { if (runtimeResource) { addResourceAuthorizationResult(result, authResp, ActionEffect.READ_RUNTIME); addResourceAuthorizationResult(result, authResp, ActionEffect.WRITE_RUNTIME); } else { addResourceAuthorizationResult(result, authResp, ActionEffect.READ_CONFIG); addResourceAuthorizationResult(result, authResp, ActionEffect.WRITE_CONFIG); } } private void addResourceAuthorizationResult(ModelNode result, ResourceAuthorization authResp, ActionEffect actionEffect) { AuthorizationResult authResult = authResp.getResourceResult(actionEffect); result.get(actionEffect == ActionEffect.READ_CONFIG || actionEffect == ActionEffect.READ_RUNTIME ? READ : WRITE).set(authResult.getDecision() == Decision.PERMIT); } private void addAttributeAuthorizationResults(ModelNode result, String attributeName, ResourceAuthorization authResp, boolean runtime) { if (runtime) { addAttributeAuthorizationResult(result, attributeName, authResp, ActionEffect.READ_RUNTIME); addAttributeAuthorizationResult(result, attributeName, authResp, ActionEffect.WRITE_RUNTIME); } else { addAttributeAuthorizationResult(result, attributeName, authResp, ActionEffect.READ_CONFIG); addAttributeAuthorizationResult(result, attributeName, authResp, ActionEffect.WRITE_CONFIG); } } private void addAttributeAuthorizationResult(ModelNode result, String attributeName, ResourceAuthorization authResp, ActionEffect actionEffect) { AuthorizationResult authorizationResult = authResp.getAttributeResult(attributeName, actionEffect); if (authorizationResult != null) { result.get(actionEffect == ActionEffect.READ_CONFIG || actionEffect == ActionEffect.READ_RUNTIME ? READ : WRITE).set(authorizationResult.getDecision() == Decision.PERMIT); } } private void addOperationAuthorizationResult(OperationContext context, ModelNode result, ModelNode operation, String operationName) { AuthorizationResult authorizationResult = context.authorizeOperation(operation); result.get(EXECUTE).set(authorizationResult.getDecision() == Decision.PERMIT); } } /** * Assembles the response to a read-resource request from the components gathered by earlier steps. */ private static class ReadResourceDescriptionAssemblyHandler implements OperationStepHandler { private final ModelNode nodeDescription; private final Map<String, ModelNode> operations; private final Map<String, ModelNode> notifications; private final Map<PathElement, ModelNode> childResources; private final ReadResourceDescriptionAccessControlContext accessControlContext; private final AccessControl accessControl; /** * Creates a ReadResourceAssemblyHandler that will assemble the response using the contents * of the given maps. * @param nodeDescription basic description of the node, of its attributes and of its child types * @param operations descriptions of the resource's operations * @param notifications descriptions of the resource's notifications * @param childResources read-resource-description response from child resources, where the key is the PathAddress * relative to the address of the operation this handler is handling and the * value is the full read-resource response. Will not be {@code null} * @param accessControlContext context for tracking access control data * @param accessControl type of access control output that is needed */ private ReadResourceDescriptionAssemblyHandler(final ModelNode nodeDescription, final Map<String, ModelNode> operations, Map<String, ModelNode> notifications, final Map<PathElement, ModelNode> childResources, final ReadResourceDescriptionAccessControlContext accessControlContext, final AccessControl accessControl) { this.nodeDescription = nodeDescription; this.operations = operations; this.notifications = notifications; this.childResources = childResources; this.accessControlContext = accessControlContext; this.accessControl = accessControl; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { for (Map.Entry<PathElement, ModelNode> entry : childResources.entrySet()) { final PathElement element = entry.getKey(); final ModelNode value = entry.getValue(); if (!value.has(FAILURE_DESCRIPTION)) { ModelNode actualValue = value.get(RESULT); if (actualValue.equals(PROXY_NO_SUCH_RESOURCE)) { nodeDescription.get(CHILDREN).remove(element.getKey()); } else { nodeDescription.get(CHILDREN, element.getKey(), MODEL_DESCRIPTION, element.getValue()).set(actualValue); } } else if (value.hasDefined(FAILURE_DESCRIPTION)) { context.getFailureDescription().set(value.get(FAILURE_DESCRIPTION)); break; } } if (operations != null) { for (Map.Entry<String, ModelNode> entry : operations.entrySet()) { nodeDescription.get(OPERATIONS.getName(), entry.getKey()).set(entry.getValue()); } } if (notifications != null) { for (Map.Entry<String, ModelNode> entry : notifications.entrySet()) { nodeDescription.get(NOTIFICATIONS.getName(), entry.getKey()).set(entry.getValue()); } } if (accessControlContext.defaultWildcardAccessControl != null && accessControlContext.localResourceAccessControlResults != null) { ModelNode accessControl = new ModelNode(); accessControl.setEmptyObject(); ModelNode defaultControl; if (accessControlContext.defaultWildcardAccessControl != null) { accessControl.get(DEFAULT).set(accessControlContext.defaultWildcardAccessControl); defaultControl = accessControlContext.defaultWildcardAccessControl; } else { //TODO this should always be present defaultControl = new ModelNode(); } if (accessControlContext.localResourceAccessControlResults != null) { ModelNode exceptions = accessControl.get(EXCEPTIONS); exceptions.setEmptyObject(); for (Map.Entry<PathAddress, ModelNode> entry : accessControlContext.localResourceAccessControlResults.entrySet()) { if (!entry.getValue().isDefined()) { //If access was denied CheckResourceAccessHandler will leave this as undefined continue; } if (!entry.getValue().equals(defaultControl)) { //This has different values to the default due to vault expressions being used for attribute values. We need to include the address //in the exception modelnode for the console to be easier able to parse it ModelNode exceptionAddr = entry.getKey().toModelNode(); ModelNode exception = entry.getValue(); exception.get(ADDRESS).set(exceptionAddr); exceptions.get(exceptionAddr.asString()).set(entry.getValue()); } } } nodeDescription.get(ACCESS_CONTROL.getName()).set(accessControl); } if (accessControl == AccessControl.TRIM_DESCRIPTONS) { //Trim unwanted data nodeDescription.get(ModelDescriptionConstants.DESCRIPTION).clear(); if (nodeDescription.hasDefined(ModelDescriptionConstants.ATTRIBUTES)) { nodeDescription.get(ModelDescriptionConstants.ATTRIBUTES).clear(); } if (nodeDescription.hasDefined(ModelDescriptionConstants.OPERATIONS)) { nodeDescription.get(ModelDescriptionConstants.OPERATIONS).clear(); } if (nodeDescription.hasDefined(CHILDREN)) { for (String child: nodeDescription.get(CHILDREN).keys()) { ModelNode childNode = nodeDescription.get(CHILDREN, child); if (childNode.isDefined()) { childNode.remove(ModelDescriptionConstants.DESCRIPTION); } } } } context.getResult().set(nodeDescription); } } static final class ReadResourceDescriptionAccessControlContext { private final PathAddress opAddress; private final List<PathAddress> parentAddresses; private List<PathAddress> localResourceAddresses = null; private ModelNode defaultWildcardAccessControl; private Map<PathAddress, ModelNode> localResourceAccessControlResults = new HashMap<PathAddress, ModelNode>(); ReadResourceDescriptionAccessControlContext(PathAddress opAddress, ReadResourceDescriptionAccessControlContext parent) { this.opAddress = opAddress; this.parentAddresses = parent != null ? parent.parentAddresses : null; } private void initLocalResourceAddresses(OperationContext context, PathAddress opAddress){ localResourceAddresses = getLocalResourceAddresses(context, opAddress); } private List<PathAddress> getLocalResourceAddresses(OperationContext context, PathAddress opAddr){ List<PathAddress> localResourceAddresses = null; if (parentAddresses == null) { if (opAddr.size() == 0) { return Collections.singletonList(PathAddress.EMPTY_ADDRESS); } else { localResourceAddresses = new ArrayList<>(); getAllActualResourceAddresses(context, localResourceAddresses, PathAddress.EMPTY_ADDRESS, opAddr); } } else { localResourceAddresses = new ArrayList<>(); for (PathAddress pathAddress : parentAddresses) { getAllActualResourceAddresses(context, localResourceAddresses, pathAddress, opAddr); } } return localResourceAddresses; } private void getAllActualResourceAddresses(OperationContext context, List<PathAddress> addresses, PathAddress currentAddress, PathAddress opAddress) { if (opAddress.size() == 0) { return; } final int length = currentAddress.size(); final PathElement currentElement = opAddress.getElement(length); if (currentElement.isWildcard()) { Resource resource; try { resource = context.readResourceFromRoot(currentAddress); } catch (UnauthorizedException e) { //We could not read the resource, now check if that is due not to having access or read-config permissions ResourceAuthorization response = context.authorizeResource(false, false); if (response.getResourceResult(ActionEffect.ADDRESS).getDecision() != Decision.PERMIT) { //We do not have access permissions return; } //We do not have read permissions, get the resource by other means //TODO revisit this, since resource.getChildXXX() should probably need some authorization as well resource = context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS); for (PathElement element : currentAddress) { resource = resource.getChild(element); } } ImmutableManagementResourceRegistration directRegistration = context.getRootResourceRegistration().getSubModel(currentAddress); Map<String, Set<String>> childAddresses = GlobalOperationHandlers.getChildAddresses(context, currentAddress, directRegistration, resource, currentElement.getKey()); Set<String> childNames = childAddresses.get(currentElement.getKey()); if (childNames != null && childNames.size() > 0) { for (String name : childNames) { PathAddress address = currentAddress.append(PathElement.pathElement(currentElement.getKey(), name)); if (addParentResource(context, addresses, address)) { if (address.size() == opAddress.size()) { addresses.add(address); } else { getAllActualResourceAddresses(context, addresses, address, opAddress); } } } } else { //There are no children, but for access control exception purposes, // add what we have so far along with the remainder of the child PathAddress addr = currentAddress.append(opAddress.subAddress(currentAddress.size())); addresses.add(addr); } } else { PathAddress address = currentAddress.append(currentElement); if (addParentResource(context, addresses, address)) { if (address.size() == opAddress.size()) { addresses.add(address); } else { getAllActualResourceAddresses(context, addresses, address, opAddress); } } } } private boolean addParentResource(OperationContext context, List<PathAddress> addresses, PathAddress address) { try { context.readResourceFromRoot(address); } catch (Resource.NoSuchResourceException nsre) { // Don't include the result return false; } catch (UnauthorizedException ue) { //We are not allowed to read it, but still we know it exists } return true; } void checkResourceAccess(final OperationContext context, final ImmutableManagementResourceRegistration registration, final ModelNode nodeDescription, Map<String, ModelNode> operations) { final ModelNode defaultAccess = Util.createOperation( opAddress.size() > 0 && !opAddress.getLastElement().isWildcard() ? GlobalOperationHandlers.CHECK_DEFAULT_RESOURCE_ACCESS : GlobalOperationHandlers.CHECK_RESOURCE_ACCESS, opAddress); defaultWildcardAccessControl = new ModelNode(); context.addStep(defaultAccess, new CheckResourceAccessHandler(registration.isRuntimeOnly(), true, defaultWildcardAccessControl, nodeDescription, operations), OperationContext.Stage.MODEL, true); for (final PathAddress address : localResourceAddresses) { final ModelNode op = Util.createOperation(GlobalOperationHandlers.CHECK_RESOURCE_ACCESS, address); final ModelNode resultHolder = new ModelNode(); localResourceAccessControlResults.put(address, resultHolder); context.addStep(op, new CheckResourceAccessHandler(registration.isRuntimeOnly(), false, resultHolder, nodeDescription, operations), OperationContext.Stage.MODEL, true); } } } private class NestedReadResourceDescriptionHandler extends ReadResourceDescriptionHandler { final ReadResourceDescriptionAccessControlContext accessControlContext; final OperationStepHandler overrideStepHandler; NestedReadResourceDescriptionHandler(ReadResourceDescriptionAccessControlContext accessControlContext) { this.accessControlContext = accessControlContext; this.overrideStepHandler = null; } NestedReadResourceDescriptionHandler(OperationStepHandler overrideStepHandler) { this.accessControlContext = null; this.overrideStepHandler = overrideStepHandler; } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { if (accessControlContext != null) { doExecute(context, operation, accessControlContext); } else { try { overrideStepHandler.execute(context, operation); } catch (Resource.NoSuchResourceException e){ //Mark it as not accessible so that the assembly handler can remove it context.getResult().set(PROXY_NO_SUCH_RESOURCE); } catch (UnauthorizedException e) { //We were not allowed to read it, the assembly handler should still allow people to see it context.getResult().set(new ModelNode()); } } } } /** * For use with the access-control parameter */ public enum AccessControl { /** No access control information should be included */ NONE("none"), /** Access control information should be included alongside the resource descriptions */ COMBINED_DESCRIPTIONS("combined-descriptions"), /** Access control information should be inclueded alongside the minimal resource descriptions */ TRIM_DESCRIPTONS("trim-descriptions") ; private static final Map<String, AccessControl> MAP; static { final Map<String, AccessControl> map = new HashMap<String, AccessControl>(); for (AccessControl directoryGrouping : values()) { map.put(directoryGrouping.localName, directoryGrouping); } MAP = map; } public static AccessControl forName(String localName) { final AccessControl value = localName != null ? MAP.get(localName.toLowerCase(Locale.ENGLISH)) : null; return value == null ? AccessControl.valueOf(localName.toUpperCase(Locale.ENGLISH)) : value; } private final String localName; AccessControl(final String localName) { this.localName = localName; } @Override public String toString() { return localName; } public ModelNode toModelNode() { return new ModelNode().set(toString()); } } }