/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jboss.as.domain.management.access; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.IDENTITY; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.function.Supplier; import java.util.stream.Collectors; import org.jboss.as.controller.AbstractAddStepHandler; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ProcessType; import org.jboss.as.controller.ReloadRequiredRemoveStepHandler; import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; import org.jboss.as.controller.ResourceDefinition; import org.jboss.as.controller.RunningMode; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.StringListAttributeDefinition; import org.jboss.as.controller.access.management.ManagementSecurityIdentitySupplier; import org.jboss.as.controller.access.management.SensitiveTargetAccessConstraintDefinition; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.domain.management.ModelDescriptionConstants; import org.jboss.as.domain.management._private.DomainManagementResolver; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.service.ServiceController.Mode; import org.jboss.msc.value.InjectedValue; import org.wildfly.security.auth.server.SecurityDomain; /** * A resource definition for the security domain to use for the identity for management operations and any additional security * domains to attempt inflow from. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ public class AccessIdentityResourceDefinition extends SimpleResourceDefinition { private static final String SECURITY_DOMAIN_CAPABILITY = "org.wildfly.security.security-domain"; private static final String MANAGEMENT_IDENTITY_CAPABILITY = "org.wildfly.management.identity"; private static final RuntimeCapability<Void> MANAGEMENT_IDENTITY_RUNTIME_CAPABILITY = RuntimeCapability.Builder.of(MANAGEMENT_IDENTITY_CAPABILITY, Void.class) .build(); public static final PathElement PATH_ELEMENT = PathElement.pathElement(ACCESS, IDENTITY); public static final SimpleAttributeDefinition SECURITY_DOMAIN = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.SECURITY_DOMAIN, ModelType.STRING, false) .setMinSize(1) .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) .setCapabilityReference(SECURITY_DOMAIN_CAPABILITY, MANAGEMENT_IDENTITY_CAPABILITY, false) .build(); public static final StringListAttributeDefinition INFLOW_SECURITY_DOMAINS = new StringListAttributeDefinition.Builder(ModelDescriptionConstants.INFLOW_SECURITY_DOMAINS) .setAllowNull(true) .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) .setCapabilityReference(SECURITY_DOMAIN_CAPABILITY, MANAGEMENT_IDENTITY_CAPABILITY, false) .build(); private static final AttributeDefinition[] ATTRIBUTES = new AttributeDefinition[] {SECURITY_DOMAIN, INFLOW_SECURITY_DOMAINS}; private AccessIdentityResourceDefinition(AbstractAddStepHandler add) { super(new Parameters(PATH_ELEMENT, DomainManagementResolver.getResolver("core.identity")) .setAddHandler(add) .setRemoveHandler(new ReloadRequiredRemoveStepHandler(MANAGEMENT_IDENTITY_RUNTIME_CAPABILITY)) .setAddRestartLevel(OperationEntry.Flag.RESTART_NONE) .setRemoveRestartLevel(OperationEntry.Flag.RESTART_RESOURCE_SERVICES) .setAccessConstraints(SensitiveTargetAccessConstraintDefinition.ACCESS_CONTROL)); } public static ResourceDefinition newInstance(final ManagementSecurityIdentitySupplier securityIdentitySupplier) { return new AccessIdentityResourceDefinition(new AccessIdentityAddHandler(securityIdentitySupplier)); } @Override public void registerAttributes(ManagementResourceRegistration resourceRegistration) { WriteAttributeHandler write = new WriteAttributeHandler(); for (AttributeDefinition attribute : ATTRIBUTES) { resourceRegistration.registerReadWriteAttribute(attribute, null, write); } } static class AccessIdentityAddHandler extends AbstractAddStepHandler { private final ManagementSecurityIdentitySupplier securityIdentitySupplier; AccessIdentityAddHandler(ManagementSecurityIdentitySupplier securityIdentitySupplier) { super(MANAGEMENT_IDENTITY_RUNTIME_CAPABILITY, ATTRIBUTES); this.securityIdentitySupplier = securityIdentitySupplier; } @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { // TODO Ekytron, this is probably way more complex than is really needed. String securityDomain = SECURITY_DOMAIN.resolveModelAttribute(context, model).asString(); final InjectedValue<SecurityDomain> securityDomainInjected = new InjectedValue<>(); final List<FutureTask<SecurityDomain>> inflowSecurityDomainFutures; IdentityService service = new IdentityService(); ServiceBuilder<Void> serviceBuilder = context.getServiceTarget().addService(MANAGEMENT_IDENTITY_RUNTIME_CAPABILITY.getCapabilityServiceName(), service) .setInitialMode(Mode.ACTIVE); serviceBuilder.addDependency(context.getCapabilityServiceName(RuntimeCapability.buildDynamicCapabilityName(SECURITY_DOMAIN_CAPABILITY, securityDomain), SecurityDomain.class), SecurityDomain.class, securityDomainInjected); ModelNode inflowDomainsModel = INFLOW_SECURITY_DOMAINS.resolveModelAttribute(context, model); if (inflowDomainsModel.isDefined()) { List<ModelNode> inflowDomainList = inflowDomainsModel.asList(); inflowSecurityDomainFutures = new ArrayList<>(inflowDomainList.size()); for (ModelNode current : inflowDomainList) { InjectedValue<SecurityDomain> inflowInjected = new InjectedValue<>(); serviceBuilder.addDependency( context.getCapabilityServiceName(RuntimeCapability.buildDynamicCapabilityName( SECURITY_DOMAIN_CAPABILITY, current.asString()), SecurityDomain.class), SecurityDomain.class, inflowInjected); inflowSecurityDomainFutures.add(toFutureTask(inflowInjected)); } } else { inflowSecurityDomainFutures = Collections.emptyList(); } List<FutureTask<SecurityDomain>> futureTasks = new ArrayList<>(inflowSecurityDomainFutures.size() + 1); final FutureTask<SecurityDomain> configuredSecurityDomainFuture = toFutureTask(securityDomainInjected); futureTasks.add(configuredSecurityDomainFuture); futureTasks.addAll(inflowSecurityDomainFutures); service.setGetSecurityDomainFutures(futureTasks); serviceBuilder.install(); securityIdentitySupplier.setConfiguredSecurityDomainSupplier(() -> toSecurityDomain(configuredSecurityDomainFuture)); if (inflowSecurityDomainFutures.size() > 0) { securityIdentitySupplier.setInflowSecurityDomainSuppliers(inflowSecurityDomainFutures.stream() .map(f -> (Supplier<SecurityDomain>) (() -> toSecurityDomain(f))).collect(Collectors.toList())); } } @Override protected boolean requiresRuntime(OperationContext context) { return (context.getProcessType() != ProcessType.EMBEDDED_SERVER || context.getRunningMode() != RunningMode.ADMIN_ONLY) && (context.getProcessType() != ProcessType.EMBEDDED_HOST_CONTROLLER); } } private static FutureTask<SecurityDomain> toFutureTask(InjectedValue<SecurityDomain> injectedValue) { return new FutureTask<>((Callable<SecurityDomain>) (() -> injectedValue.getValue())); } private static SecurityDomain toSecurityDomain(final FutureTask<SecurityDomain> futureTask) { try { return futureTask.get(); } catch (InterruptedException | ExecutionException e) { throw new IllegalStateException(e); } } static class IdentityService implements Service<Void> { private List<FutureTask<SecurityDomain>> getSecurityDomainFutures = Collections.emptyList(); @Override public void start(StartContext context) throws StartException { if (getSecurityDomainFutures != null) { getSecurityDomainFutures.forEach(FutureTask::run); } } @Override public void stop(StopContext context) { } void setGetSecurityDomainFutures(List<FutureTask<SecurityDomain>> getSecurityDomainFutures) { this.getSecurityDomainFutures = getSecurityDomainFutures; } @Override public Void getValue() throws IllegalStateException, IllegalArgumentException { return null; } } static class WriteAttributeHandler extends ReloadRequiredWriteAttributeHandler { public WriteAttributeHandler() { super(ATTRIBUTES); } @Override protected boolean requiresRuntime(OperationContext context) { return context.isBooting() == false; } } }