/* * JBoss, Home of Professional Open Source. * Copyright 2014, 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.domain.management.security; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHENTICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHORIZATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.GROUP_SEARCH; 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_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.USERNAME_TO_DN; import static org.jboss.as.domain.management.ModelDescriptionConstants.CONTAINS; import static org.jboss.as.domain.management.ModelDescriptionConstants.FLUSH_CACHE; import static org.jboss.as.domain.management.ModelDescriptionConstants.SECURITY_REALM; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.jboss.as.controller.ModelVersion; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationContext.Stage; import org.jboss.as.controller.AttributeDefinition; 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.ResourceDefinition; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.SimpleOperationDefinition; import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.client.helpers.MeasurementUnit; import org.jboss.as.controller.descriptions.common.ControllerResolver; import org.jboss.as.controller.operations.common.Util; 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.controller.registry.Resource; import org.jboss.as.domain.management.logging.DomainManagementLogger; import org.jboss.as.domain.management.ModelDescriptionConstants; import org.jboss.as.domain.management.security.LdapSearcherCache.Predicate; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceRegistry; /** * {@link ResourceDefinition} for a LDAP caching. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ public class LdapCacheResourceDefinition extends SimpleResourceDefinition { private static final CacheDefintionValidatingHandler VALIDATION_INSTANCE = new CacheDefintionValidatingHandler(); /* * Configuration Attributes */ public static final SimpleAttributeDefinition EVICTION_TIME = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.EVICTION_TIME, ModelType.INT, true) .setAllowExpression(true) .setDefaultValue(new ModelNode().set(900)) .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) .setMeasurementUnit(MeasurementUnit.SECONDS) .build(); public static final SimpleAttributeDefinition CACHE_FAILURES = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.CACHE_FAILURES, ModelType.BOOLEAN, true) .setAllowExpression(true) .setDefaultValue(new ModelNode().set(false)) .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) .build(); public static final SimpleAttributeDefinition MAX_CACHE_SIZE = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.MAX_CACHE_SIZE, ModelType.INT, true) .setAllowExpression(true) .setDefaultValue(new ModelNode().set(0)) .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) .build(); /* * Runtime Attributes */ // Current Size - int public static final SimpleAttributeDefinition CACHE_SIZE = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.CACHE_SIZE, ModelType.INT) .setStorageRuntime() .setRuntimeServiceNotRequired() .setMeasurementUnit(MeasurementUnit.SECONDS) .build(); // (Other options are, max size, min size, max age, min age, average age. /* * Runtime Operations */ public static final SimpleAttributeDefinition NAME = new SimpleAttributeDefinition(ModelDescriptionConstants.NAME, ModelType.STRING, true); public static final SimpleAttributeDefinition NAME_REQUIRED = new SimpleAttributeDefinition(ModelDescriptionConstants.NAME, ModelType.STRING, false); public static final SimpleAttributeDefinition DISTINGUISHED_NAME = new SimpleAttributeDefinition(ModelDescriptionConstants.DISTINGUISHED_NAME, ModelType.STRING, true); public static final SimpleOperationDefinition FLUSH_CACHE_NAME_ONLY = new SimpleOperationDefinitionBuilder(ModelDescriptionConstants.FLUSH_CACHE, ControllerResolver.getResolver("core.management.security-realm.ldap.cache")) .setEntryType(OperationEntry.EntryType.PUBLIC) .addParameter(NAME) .setRuntimeOnly() .build(); public static final SimpleOperationDefinition FLUSH_CACHE_FULL = new SimpleOperationDefinitionBuilder(ModelDescriptionConstants.FLUSH_CACHE, ControllerResolver.getResolver("core.management.security-realm.ldap.cache")) .setEntryType(OperationEntry.EntryType.PUBLIC) .addParameter(NAME) .addParameter(DISTINGUISHED_NAME) .setRuntimeOnly() .build(); public static final SimpleOperationDefinition CONTAINS_NAME_ONLY = new SimpleOperationDefinitionBuilder(ModelDescriptionConstants.CONTAINS, ControllerResolver.getResolver("core.management.security-realm.ldap.cache")) .setEntryType(OperationEntry.EntryType.PUBLIC) .addParameter(NAME_REQUIRED) .setRuntimeOnly() .setReplyValueType(ModelType.BOOLEAN) .build(); public static final SimpleOperationDefinition CONTAINS_FULL = new SimpleOperationDefinitionBuilder(ModelDescriptionConstants.CONTAINS, ControllerResolver.getResolver("core.management.security-realm.ldap.cache")) .setEntryType(OperationEntry.EntryType.PUBLIC) .addParameter(NAME) .addParameter(DISTINGUISHED_NAME) .setRuntimeOnly() .setReplyValueType(ModelType.BOOLEAN) .build(); private static final OperationStepHandler NAME_ONLY_HANDLER = new NameOnlyOpHandler(); private static final OperationStepHandler FULL_HANDLER = new FullOpHandler(); private final SimpleAttributeDefinition[] configurationAttributes; private final SimpleAttributeDefinition[] runtimeAttributes; private final SimpleOperationDefinition[] runtimeOperations; private final OperationStepHandler runtimeStepHandler; private LdapCacheResourceDefinition(final PathElement pathElement, final SimpleAttributeDefinition[] configurationAttributes, final SimpleAttributeDefinition[] runtimeAttributes, final SimpleOperationDefinition[] runtimeOperations, final OperationStepHandler runtimeStepHandler) { super(pathElement, ControllerResolver.getDeprecatedResolver(SecurityRealmResourceDefinition.DEPRECATED_PARENT_CATEGORY, "core.management.security-realm.ldap.cache"), new CacheChildAddHandler(configurationAttributes), new SecurityRealmChildRemoveHandler( false), OperationEntry.Flag.RESTART_RESOURCE_SERVICES, OperationEntry.Flag.RESTART_RESOURCE_SERVICES); this.configurationAttributes = configurationAttributes; this.runtimeAttributes = runtimeAttributes; this.runtimeOperations = runtimeOperations; this.runtimeStepHandler = runtimeStepHandler; setDeprecated(ModelVersion.create(1, 7)); } private static ResourceDefinition create(final PathElement pathElement, final CacheFor cacheFor) { SimpleAttributeDefinition[] configurationAttributes = new SimpleAttributeDefinition[] { EVICTION_TIME, CACHE_FAILURES, MAX_CACHE_SIZE }; SimpleAttributeDefinition[] runtimeAttributes = new SimpleAttributeDefinition[] { CACHE_SIZE }; final SimpleOperationDefinition[] runtimeOperations; final OperationStepHandler runtimeHandler; switch (cacheFor) { case AuthUser: runtimeOperations = new SimpleOperationDefinition[] { FLUSH_CACHE_NAME_ONLY, CONTAINS_NAME_ONLY }; runtimeHandler = NAME_ONLY_HANDLER; break; default: runtimeOperations = new SimpleOperationDefinition[] { FLUSH_CACHE_FULL, CONTAINS_FULL }; runtimeHandler = FULL_HANDLER; } return new LdapCacheResourceDefinition(pathElement, configurationAttributes, runtimeAttributes, runtimeOperations, runtimeHandler); } public static ResourceDefinition createByAccessTime(final CacheFor cacheFor) { return create(PathElement.pathElement(ModelDescriptionConstants.CACHE, ModelDescriptionConstants.BY_ACCESS_TIME), cacheFor); } public static ResourceDefinition createBySearchTime(final CacheFor cacheFor) { return create(PathElement.pathElement(ModelDescriptionConstants.CACHE, ModelDescriptionConstants.BY_SEARCH_TIME), cacheFor); } /** * Creates an operations that targets the valiadating handler. * * @param operationToValidate the operation that this handler will validate * @return the validation operation */ private static ModelNode createOperation(final ModelNode operationToValidate) { PathAddress pa = PathAddress.pathAddress(operationToValidate.require(OP_ADDR)); PathAddress validationAddress = pa.subAddress(0, pa.size() - 1); return Util.getEmptyOperation("validate-cache", validationAddress.toModelNode()); } public enum CacheFor { AuthUser, AuthzUser, AuthzGroup } @Override public void registerAttributes(ManagementResourceRegistration resourceRegistration) { super.registerAttributes(resourceRegistration); OperationStepHandler writeHandler = new SecurityRealmChildWriteAttributeHandler(configurationAttributes); for (SimpleAttributeDefinition attr : configurationAttributes) { resourceRegistration.registerReadWriteAttribute(attr, null, writeHandler); } for (SimpleAttributeDefinition attr : runtimeAttributes) { resourceRegistration.registerReadOnlyAttribute(attr, runtimeStepHandler); } } @Override public void registerOperations(ManagementResourceRegistration resourceRegistration) { super.registerOperations(resourceRegistration); for (SimpleOperationDefinition op : runtimeOperations) { resourceRegistration.registerOperationHandler(op, runtimeStepHandler); } } private abstract static class BaseRuntimeOpHandler<K> implements OperationStepHandler { private static final Set<String> VALID_OPS; static { Set<String> ops = new HashSet<String>(3); ops.add(READ_ATTRIBUTE_OPERATION); ops.add(FLUSH_CACHE); ops.add(CONTAINS); VALID_OPS = Collections.unmodifiableSet(ops); } @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { final String operationName = operation.get(OP).asString(); if (VALID_OPS.contains(operationName)) { context.addStep(new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { if (READ_ATTRIBUTE_OPERATION.equals(operationName)) { readAttribute(context, operation); } else if (FLUSH_CACHE.equals(operationName)) { flushCache(context, operation); } else if (CONTAINS.equals(operationName)) { contains(context, operation); } } }, Stage.RUNTIME); } } public abstract void flushCache(OperationContext context, ModelNode operation) throws OperationFailedException; public abstract void contains(OperationContext context, ModelNode operation) throws OperationFailedException; public void readAttribute(OperationContext context, ModelNode operation) throws OperationFailedException { String name = operation.get(ModelDescriptionConstants.NAME).asString(); if (ModelDescriptionConstants.CACHE_SIZE.equals(name)) { LdapSearcherCache<?, K> ldapCacheService = lookupService(context, operation); context.getResult().set(ldapCacheService.getCurrentSize()); } } protected LdapSearcherCache<?, K> lookupService(final OperationContext context, final ModelNode operation) throws OperationFailedException { String realmName = null; boolean forAuthentication = false; boolean forUserSearch = false; PathAddress address = PathAddress.pathAddress(operation.get(OP_ADDR)); for (PathElement current : address) { String key = current.getKey(); if (SECURITY_REALM.equals(key)) { realmName = current.getValue(); } else if (AUTHENTICATION.equals(key)) { forAuthentication = true; forUserSearch = true; } else if (AUTHORIZATION .equals(key)) { forAuthentication = false; } else if (USERNAME_TO_DN.equals(key)) { forUserSearch = true; } else if (GROUP_SEARCH.equals(key)) { forUserSearch = false; } } ServiceName serviceName = LdapSearcherCache.ServiceUtil.createServiceName(forAuthentication, forUserSearch, realmName); ServiceRegistry registry = context.getServiceRegistry(true); ServiceController<LdapSearcherCache<?, K>> service = (ServiceController<LdapSearcherCache<?, K>>) registry.getRequiredService(serviceName); try { return service.awaitValue(); } catch (InterruptedException e) { throw new OperationFailedException(e); } } } private static class NameOnlyOpHandler extends BaseRuntimeOpHandler<String> { @Override public void flushCache(OperationContext context, ModelNode operation) throws OperationFailedException { LdapSearcherCache<?, String> ldapCacheService = lookupService(context, operation); if (operation.hasDefined(ModelDescriptionConstants.NAME)) { String name = operation.require(ModelDescriptionConstants.NAME).asString(); ldapCacheService.clear(name); } else { ldapCacheService.clearAll(); } } @Override public void contains(OperationContext context, ModelNode operation) throws OperationFailedException { LdapSearcherCache<?, String> ldapCacheService = lookupService(context, operation); String name = operation.require(ModelDescriptionConstants.NAME).asString(); context.getResult().set(ldapCacheService.contains(name)); } } private static class FullOpHandler extends BaseRuntimeOpHandler<LdapEntry> { @Override public void flushCache(OperationContext context, ModelNode operation) throws OperationFailedException { LdapSearcherCache<?, LdapEntry> ldapCacheService = lookupService(context, operation); String name = null; String distinguishedName = null; if (operation.hasDefined(ModelDescriptionConstants.NAME)) { name = operation.require(ModelDescriptionConstants.NAME).asString(); } if (operation.hasDefined(ModelDescriptionConstants.DISTINGUISHED_NAME)) { distinguishedName = operation.require(ModelDescriptionConstants.DISTINGUISHED_NAME).asString(); } if (name == null && distinguishedName == null) { ldapCacheService.clearAll(); } else if (name != null && distinguishedName != null) { ldapCacheService.clear(new LdapEntry(name, distinguishedName)); } else { ldapCacheService.clear(new LdapEntryPredicate(name, distinguishedName)); } } @Override public void contains(OperationContext context, ModelNode operation) throws OperationFailedException { LdapSearcherCache<?, LdapEntry> ldapCacheService = lookupService(context, operation); String name = null; String distinguishedName = null; if (operation.hasDefined(ModelDescriptionConstants.NAME)) { name = operation.require(ModelDescriptionConstants.NAME).asString(); } if (operation.hasDefined(ModelDescriptionConstants.DISTINGUISHED_NAME)) { distinguishedName = operation.require(ModelDescriptionConstants.DISTINGUISHED_NAME).asString(); } if (name == null && distinguishedName == null) { context.getResult().set(false); // TODO - Maybe report an error instead. } else if (name != null && distinguishedName != null) { context.getResult().set(ldapCacheService.contains(new LdapEntry(name, distinguishedName))); } else { boolean contains = ldapCacheService.count(new LdapEntryPredicate(name, distinguishedName)) > 0; context.getResult().set(contains); } } } private static class LdapEntryPredicate implements Predicate<LdapEntry> { private final String name; private final String distinguishedName; private LdapEntryPredicate(final String name, final String distinguishedName) { this.name = name; this.distinguishedName = distinguishedName; } @Override public boolean matches(LdapEntry key) { if (name != null) { if (name.equals(key.getSimpleName()) == false) { return false; } } if (distinguishedName != null) { if (distinguishedName.equals(key.getDistinguishedName()) == false) { return false; } } return true; } } static class CacheChildAddHandler extends SecurityRealmChildAddHandler { public CacheChildAddHandler(AttributeDefinition[] attributeDefinitions) { super(false, false, attributeDefinitions); } @Override protected void updateModel(OperationContext context, ModelNode operation) throws OperationFailedException { super.updateModel(context, operation); ModelNode validateOp = createOperation(operation); context.addStep(validateOp, VALIDATION_INSTANCE, Stage.MODEL); } } private static class CacheDefintionValidatingHandler implements OperationStepHandler { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS); Set<String> children = resource.getChildrenNames(ModelDescriptionConstants.CACHE); if (children.size() > 1) { String realmName = ManagementUtil.getSecurityRealmName(operation); throw DomainManagementLogger.ROOT_LOGGER.multipleCacheConfigurationsDefined(realmName); } } } }