From 4776582a6a247cc5b026006c19e99f155f1cec10 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Wed, 1 Oct 2014 13:55:39 -0400 Subject: [PATCH 1/6] Old experiment with keycloak-server.json embedded into standalone.xml --- .gitignore | 3 +- .../main/module.xml | 1 + integration/wildfly-subsystem/pom.xml | 29 ++- ...cloakAdapterConfigDeploymentProcessor.java | 17 +- .../KeycloakAdapterConfigService.java | 33 +++- .../KeycloakDependencyProcessor.java | 40 ++++- .../extension/KeycloakExtension.java | 7 +- .../extension/KeycloakStructureProcessor.java | 80 +++++++++ .../extension/KeycloakSubsystemAdd.java | 15 +- .../extension/KeycloakSubsystemParser.java | 38 ++++ .../authserver/AuthServerAddHandler.java | 88 +++++++++ .../authserver/AuthServerDefinition.java | 134 ++++++++++++++ .../authserver/AuthServerRemoveHandler.java | 54 ++++++ .../extension/authserver/AuthServerUtil.java | 168 ++++++++++++++++++ .../AuthServerWriteAttributeHandler.java | 41 +++++ .../extension/LocalDescriptions.properties | 9 + pom.xml | 5 + .../resources/KeycloakApplication.java | 23 ++- 18 files changed, 768 insertions(+), 17 deletions(-) create mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakStructureProcessor.java create mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerAddHandler.java create mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java create mode 100644 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java create mode 100644 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java create mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java diff --git a/.gitignore b/.gitignore index 319769bf2a..44612eb8b4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,10 +9,11 @@ .settings .classpath - # NetBeans # ############ +nbactions.xml nb-configuration.xml +catalog.xml # Compiled source # ################### diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml index 0bc0ec9f78..22d6beacb8 100755 --- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml +++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml @@ -31,6 +31,7 @@ + diff --git a/integration/wildfly-subsystem/pom.xml b/integration/wildfly-subsystem/pom.xml index d776e6006a..52475151c9 100755 --- a/integration/wildfly-subsystem/pom.xml +++ b/integration/wildfly-subsystem/pom.xml @@ -42,11 +42,9 @@ org.apache.maven.plugins maven-surefire-plugin - 2.8.1 false true - -Xmx512m jboss.home @@ -56,9 +54,34 @@ **/*TestCase.java - once + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + compile + + copy + + + + + org.keycloak + keycloak-server + ${project.version} + war + true + ${project.build.directory}/classes/deployments + auth-server.war + + + + + + diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java index 55e47eba7f..cc5b73c5da 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -31,6 +31,7 @@ import org.keycloak.subsystem.logging.KeycloakLogger; import java.util.ArrayList; import java.util.List; +import org.jboss.as.ee.component.EEModuleDescription; /** * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension. @@ -45,9 +46,9 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; - public static final Phase PHASE = Phase.INSTALL; - // Seems wise to have this run after INSTALL_WAR_DEPLOYMENT - public static final int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT - 1; + public static final Phase PHASE = Phase.POST_MODULE; + // This needs to run just before bean validator factory + public static final int PRIORITY = Phase.POST_MODULE_VALIDATOR_FACTORY - 1; // not sure if we need this yet, keeping here just in case protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) { @@ -73,6 +74,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); String deploymentName = deploymentUnit.getName(); + System.out.println(">>>>> deploymentName=" + deploymentName); KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); //log.info("********* CHECK KEYCLOAK DEPLOYMENT: " + deploymentName); if (service.isKeycloakDeployment(deploymentName)) { @@ -99,6 +101,15 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP webMetaData = new JBossWebMetaData(); warMetaData.setMergedJBossWebMetaData(webMetaData); } + + if (service.isKeycloakServerDeployment(deploymentName)) { + final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION); + String webContext = service.getWebContext(deploymentName); + if (webContext == null) throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Auth Server"); + description.setModuleName(webContext); + return; + } + LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); if (loginConfig == null) { loginConfig = new LoginConfigMetaData(); diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java index eb820fcff8..ee91ef3152 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -53,6 +53,11 @@ public final class KeycloakAdapterConfigService implements Service realms = new HashMap(); private Map deployments = new HashMap(); + // key=server deployment name; value=json + private Map serverDeployments = new HashMap(); + // key=server deployment name; value=web-context + private Map webContexts = new HashMap(); + private KeycloakAdapterConfigService() { } @@ -72,6 +77,24 @@ public final class KeycloakAdapterConfigService implements Service list) throws XMLStreamException { + String authServerName = readNameAttribute(reader); + ModelNode addAuthServer = new ModelNode(); + addAuthServer.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(AuthServerDefinition.TAG_NAME, authServerName)); + addAuthServer.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + SimpleAttributeDefinition def = AuthServerDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown auth-server tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addAuthServer, reader); + } + + list.add(addAuthServer); + } + private void readRealm(XMLExtendedStreamReader reader, List list) throws XMLStreamException { String realmName = readNameAttribute(reader); ModelNode addRealm = new ModelNode(); @@ -157,11 +178,28 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • > newControllers) throws OperationFailedException { + } +} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java new file mode 100755 index 0000000000..685a910355 --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java @@ -0,0 +1,134 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.operations.validation.ParameterValidator; +import org.jboss.as.controller.registry.OperationEntry; +import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; +import org.keycloak.subsystem.extension.KeycloakExtension; + +/** + * Defines attributes and operations for an Auth Server + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class AuthServerDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "auth-server"; + + protected static final SimpleAttributeDefinition ENABLED = + new SimpleAttributeDefinitionBuilder("enabled", ModelType.BOOLEAN, true) + .setXmlName("enabled") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(true)) + .setRestartAllServices() + .build(); + + protected static final SimpleAttributeDefinition WEB_CONTEXT = + new SimpleAttributeDefinitionBuilder("web-context", ModelType.STRING, true) + .setXmlName("web-context") + .setAllowExpression(true) + .setDefaultValue(new ModelNode("auth")) + .setValidator(new WebContextValidator()) + .setRestartAllServices() + .build(); + + /* protected static final SimpleAttributeDefinition KEYCLOAK_SERVER_JSON = + new SimpleAttributeDefinitionBuilder("keycloak-server-json", ModelType.STRING, true) + .setXmlName("keycloak-server-json") + .setAllowExpression(true) + .setRestartAllServices() + .build(); */ + + public static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.add(ENABLED); + ALL_ATTRIBUTES.add(WEB_CONTEXT); + //ALL_ATTRIBUTES.add(KEYCLOAK_SERVER_JSON); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private static AuthServerWriteAttributeHandler attrHandler = new AuthServerWriteAttributeHandler(ALL_ATTRIBUTES); + + public AuthServerDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + AuthServerAddHandler.INSTANCE, + AuthServerRemoveHandler.INSTANCE, + null, + OperationEntry.Flag.RESTART_ALL_SERVICES); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { + resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler); + } + } + + public static SimpleAttributeDefinition lookup(String name) { + return DEFINITION_LOOKUP.get(name); + } + + private static class WebContextValidator implements ParameterValidator { + + @Override + public void validateParameter(String paramName, ModelNode value) throws OperationFailedException { + String strValue = value.asString(); + if (KeycloakAdapterConfigService.INSTANCE.isWebContextUsed(strValue)) { + throw new OperationFailedException("Can not set web-context to '" + strValue + "'. web-context must be unique among all deployments."); + } + } + + @Override + public void validateResolvedParameter(String paramName, ModelNode value) throws OperationFailedException { + String strValue = value.asString(); + if (KeycloakAdapterConfigService.INSTANCE.isWebContextUsed(strValue)) { + throw new OperationFailedException("Can not set web-context to '" + strValue + "'. web-context must be unique among all deployments."); + } + } + + } +} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java new file mode 100644 index 0000000000..7f92ab2ae8 --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.dmr.ModelNode; +import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; + +/** + * Remove an auth-server from a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public final class AuthServerRemoveHandler extends AbstractRemoveStepHandler { + + public static AuthServerRemoveHandler INSTANCE = new AuthServerRemoveHandler(); + + private AuthServerRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + //KeycloakAdapterConfigService.INSTANCE.removeAuthServer() + System.out.println("*** performRuntime ** operation"); + System.out.println(operation.toString()); + System.out.println("*** performRuntime ** model"); + System.out.println(model.toString()); + String deploymentName = Util.getNameFromAddress(operation.get(ADDRESS)); + System.out.println("*** authServerName=" + deploymentName); + if (!deploymentName.toLowerCase().endsWith(".war")) { + deploymentName += ".war"; + } + KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName); + } +} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java new file mode 100644 index 0000000000..a7a4fd798d --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java @@ -0,0 +1,168 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import org.jboss.as.controller.OperationContext; +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 static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PERSISTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleIdentifier; +import org.jboss.modules.ModuleLoadException; +import org.jboss.modules.Resource; +import org.jboss.modules.filter.PathFilter; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class AuthServerUtil { + + private static final ModuleIdentifier KEYCLOAK_SUBSYSTEM = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-subsystem"); + + private static URL authServerUrl = null; + + private static String defaultAuthServerJson = ""; + + static String getDefaultAuthServerJson() { + if (authServerUrl == null) getWarUrl(); + return defaultAuthServerJson; + } + + // Can return the URL, null, or throw IllegalStateException + // This also finds the defaultAuthServerJson and sets the instance var for it. + private static URL getWarUrl() throws IllegalStateException { + if (authServerUrl != null) { // only need to find this once + return authServerUrl; + } + + Module module; + try { + module = Module.getModuleFromCallerModuleLoader(KEYCLOAK_AUTH_SERVER); + } catch (ModuleLoadException e) { + throw new IllegalStateException("Keycloak Auth Server not installed as a module.", e); + } + + URL warUrl = null; + try { + java.util.Iterator rscIterator = module.iterateResources(new PathFilter() { + @Override + public boolean accept(String string) { + return true; + } + }); + + // There should be only one war resource, the auth server + while (rscIterator.hasNext()) { + Resource rsc = rscIterator.next(); + System.out.println("rsc.getName()=" + rsc.getName()); + URL url = rsc.getURL(); + if (url.toExternalForm().toLowerCase().endsWith(".war")) { + warUrl = url; + setDefaultAuthServerJson(rsc); + break; + } + } + } catch (ModuleLoadException e) { + throw new IllegalStateException(e); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + authServerUrl = warUrl; + System.out.println("&&&&& authServerUrl=" + authServerUrl); + return authServerUrl; + } + + // return deploymentName this will be started under + static String addStepToStartAuthServer(OperationContext context, ModelNode operation) throws OperationFailedException { + + PathAddress authServerAddr = PathAddress.pathAddress(operation.get(ADDRESS)); + String deploymentName = authServerAddr.getElement(1).getValue(); + if (!deploymentName.toLowerCase().endsWith(".war")) { + deploymentName += ".war"; + } + + PathAddress deploymentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); + ModelNode op = Util.createOperation(ADD, deploymentAddress); + op.get(ENABLED).set(true); + op.get(PERSISTENT).set(false); // prevents writing this deployment out to standalone.xml + + URL warUrl = null; + try { + warUrl = getWarUrl(); + } catch (IllegalStateException e) { + throw new OperationFailedException(e); + } + + if (warUrl == null) { + throw new OperationFailedException("Keycloak Auth Server WAR not found in keycloak-auth-server module"); + } + + String urlString = warUrl.toExternalForm(); + System.out.println(warUrl); + ModelNode contentItem = new ModelNode(); + contentItem.get(URL).set(urlString); + op.get(CONTENT).add(contentItem); + System.out.println("****** operation ************"); + System.out.println(op.toString()); + ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); + OperationStepHandler handler = rootResourceRegistration.getOperationHandler(deploymentAddress, ADD); + context.addStep(op, handler, OperationContext.Stage.MODEL); + + return deploymentName; + } + + private static void setDefaultAuthServerJson(Resource rsc) throws IOException { + JarInputStream jarStream = null; + try { + jarStream = new JarInputStream(rsc.openStream()); + JarEntry je; + while ((je = jarStream.getNextJarEntry()) != null) { + if (!je.getName().equals("WEB-INF/classes/META-INF/keycloak-server.json")) continue; + + int len = 0; + byte[] buffer = new byte[1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((len = jarStream.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + + defaultAuthServerJson = baos.toString(); + return; + } + } finally { + jarStream.close(); + } + } +} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java new file mode 100755 index 0000000000..b7e53ea753 --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinition; + +import java.util.List; +import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; + +/** + * Update an attribute on an Auth Server. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class AuthServerWriteAttributeHandler extends ReloadRequiredWriteAttributeHandler { + + public AuthServerWriteAttributeHandler(List definitions) { + this(definitions.toArray(new AttributeDefinition[definitions.size()])); + } + + public AuthServerWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + +} diff --git a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties index 524ae4d60c..533234b85d 100755 --- a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties +++ b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -1,9 +1,18 @@ keycloak.subsystem=Keycloak subsystem keycloak.subsystem.add=Operation Adds Keycloak subsystem keycloak.subsystem.remove=Operation removes Keycloak subsystem +keycloak.subsystem.auth-server=Keycloak Auth Server keycloak.subsystem.realm=A Keycloak realm. keycloak.subsystem.secure-deployment=A deployment secured by Keycloak. + +keycloak.auth-server=A Keycloak Auth Server +keycloak.auth-server.add=Add an Auth Server to the subsystem. +keycloak.auth-server.remove=Remove an Auth Server from the subsystem. +keycloak.auth-server.enabled=Enable or disable the Auth Server. +keycloak.auth-server.keycloak-server-json=Externalized version of keycloak-server.json +keycloak.auth-server.web-context=Web context the auth-server will use. Also, the module name of the auth-server deployment. + keycloak.realm=A Keycloak realm. keycloak.realm.add=Add a realm definition to the subsystem. keycloak.realm.remove=Remove a realm from the subsystem. diff --git a/pom.xml b/pom.xml index f6164ff828..80d627c49a 100755 --- a/pom.xml +++ b/pom.xml @@ -485,6 +485,11 @@ + + org.apache.maven.plugins + maven-dependency-plugin + 2.8 + org.apache.maven.plugins maven-surefire-plugin diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index a2d5a60d29..89e5d40d7c 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -41,6 +41,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import org.keycloak.adapters.AdapterConstants; /** * @author Bill Burke @@ -57,7 +58,7 @@ public class KeycloakApplication extends Application { protected String contextPath; public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { - loadConfig(); + loadConfig(context); this.sessionFactory = createSessionFactory(); @@ -102,6 +103,26 @@ public class KeycloakApplication extends Application { return uriInfo.getBaseUriBuilder().replacePath(getContextPath()).build(); } + private static void loadConfig(ServletContext context) { + String json = context.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); + if (json == null) { + loadConfig(); // from file + } else { + loadConfig(json); // from ServletContext/Keycloak subsystem + } + } + + private static void loadConfig(String json) { + try { + JsonNode node = new ObjectMapper().readTree(json); + Config.init(new JsonConfigProvider(node)); + } catch (IOException e) { + throw new RuntimeException("Failed to load config", e); + } + + log.info("Loaded config from Keycloak subsystem"); + } + public static void loadConfig() { try { URL config = null; From f9215e961dfc8f6fed85159d94fa5c9b93b28aad Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 21 Oct 2014 13:04:18 -0400 Subject: [PATCH 2/6] Incremental commit because I haven't done one in awhile. --- distribution/appliance-dist/assembly.xml | 2 +- .../src/main/xslt/standalone.xsl | 22 +- distribution/modules/lib.xml | 7 +- distribution/modules/pom.xml | 65 +++- .../main/module.xml | 6 + integration/keycloak-feature-pack/pom.xml | 83 ++++ .../keycloak-adapter-core/main/module.xml | 39 ++ .../keycloak/keycloak-core/main/module.xml | 38 ++ .../main/module.xml | 38 ++ .../keycloak-undertow-adapter/main/module.xml | 44 +++ .../keycloak-wildfly-adapter/main/module.xml | 46 +++ .../main/module.xml | 47 +++ integration/pom.xml | 1 + integration/wildfly-subsystem/pom.xml | 28 +- ...cloakAdapterConfigDeploymentProcessor.java | 23 +- .../KeycloakAdapterConfigService.java | 37 +- .../KeycloakDependencyProcessor.java | 33 +- .../extension/KeycloakStructureProcessor.java | 80 ---- .../extension/KeycloakSubsystemAdd.java | 1 - .../extension/SecureDeploymentDefinition.java | 1 + .../authserver/AddProviderHandler.java | 35 ++ .../authserver/AuthServerAddHandler.java | 40 +- .../authserver/AuthServerDefinition.java | 14 +- .../authserver/AuthServerRemoveHandler.java | 35 +- .../extension/authserver/AuthServerUtil.java | 353 ++++++++++++++---- .../AuthServerWriteAttributeHandler.java | 61 ++- .../authserver/ManageOverlayHandler.java | 99 +++++ .../extension/LocalDescriptions.properties | 4 +- pom.xml | 94 +++-- .../resources/KeycloakApplication.java | 23 +- 30 files changed, 1015 insertions(+), 384 deletions(-) create mode 100644 integration/keycloak-feature-pack/pom.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml create mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml delete mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakStructureProcessor.java create mode 100644 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AddProviderHandler.java create mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml index 0e788d8c34..acaa91fa78 100755 --- a/distribution/appliance-dist/assembly.xml +++ b/distribution/appliance-dist/assembly.xml @@ -48,7 +48,7 @@ ${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF - keycloak/standalone/configuration + keycloak/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/overlays keycloak-server.json diff --git a/distribution/appliance-dist/src/main/xslt/standalone.xsl b/distribution/appliance-dist/src/main/xslt/standalone.xsl index 7b8ba53ed6..7ed378f148 100755 --- a/distribution/appliance-dist/src/main/xslt/standalone.xsl +++ b/distribution/appliance-dist/src/main/xslt/standalone.xsl @@ -34,7 +34,12 @@ - + + + true + auth + + @@ -56,7 +61,7 @@ + longer works --> @@ -67,13 +72,24 @@ + longer works --> + + + + + + + + + + + diff --git a/distribution/modules/lib.xml b/distribution/modules/lib.xml index 6e2f849367..3d9438a4a4 100755 --- a/distribution/modules/lib.xml +++ b/distribution/modules/lib.xml @@ -52,7 +52,12 @@ - + + + + diff --git a/distribution/modules/pom.xml b/distribution/modules/pom.xml index afc41ed6fb..503ea21e6c 100755 --- a/distribution/modules/pom.xml +++ b/distribution/modules/pom.xml @@ -1,26 +1,26 @@ +~ JBoss, Home of Professional Open Source. +~ Copyright 2010, 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. +--> + + org.apache.maven.plugins + maven-dependency-plugin + + + copy + compile + + copy + + + + + org.keycloak + keycloak-server + ${project.version} + war + true + ${project.build.directory}/modules/org/keycloak/keycloak-wildfly-subsystem/main/auth-server + + + + + + diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml index 22d6beacb8..476147922a 100755 --- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml +++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml @@ -23,7 +23,13 @@ --> + + + + + + diff --git a/integration/keycloak-feature-pack/pom.xml b/integration/keycloak-feature-pack/pom.xml new file mode 100644 index 0000000000..b2560302b8 --- /dev/null +++ b/integration/keycloak-feature-pack/pom.xml @@ -0,0 +1,83 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-parent + 1.1.0-Alpha1-SNAPSHOT + ../../pom.xml + + + keycloak-feature-pack + Keycloak Feature Pack + + jar + + + + + + org.wildfly.core + wildfly-core-feature-pack + zip + + + * + * + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.8.1 + + false + true + -Xmx512m + + + jboss.home + ${jboss.home} + + + + **/*TestCase.java + + once + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml new file mode 100644 index 0000000000..0def6052f1 --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml new file mode 100644 index 0000000000..da3e18b810 --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml new file mode 100644 index 0000000000..89b0a6de2e --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml new file mode 100644 index 0000000000..dedbd945ec --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml new file mode 100644 index 0000000000..ceb9b4bf89 --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml new file mode 100644 index 0000000000..9ccd15f752 --- /dev/null +++ b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/pom.xml b/integration/pom.xml index 0c52175399..06997d2090 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -28,5 +28,6 @@ js installed admin-client + keycloak-feature-pack diff --git a/integration/wildfly-subsystem/pom.xml b/integration/wildfly-subsystem/pom.xml index 52475151c9..ed0a337428 100755 --- a/integration/wildfly-subsystem/pom.xml +++ b/integration/wildfly-subsystem/pom.xml @@ -55,33 +55,7 @@ **/*TestCase.java - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy - compile - - copy - - - - - org.keycloak - keycloak-server - ${project.version} - war - true - ${project.build.directory}/classes/deployments - auth-server.war - - - - - - + diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java index cc5b73c5da..ea26666714 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -31,7 +31,10 @@ import org.keycloak.subsystem.logging.KeycloakLogger; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.jboss.as.ee.component.EEModuleDescription; +import org.jboss.as.server.deployment.Attachments; +import org.jboss.as.server.deployment.MountedDeploymentOverlay; /** * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension. @@ -53,7 +56,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP // not sure if we need this yet, keeping here just in case protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) { String deploymentName = deploymentUnit.getName(); - if (!service.isKeycloakDeployment(deploymentName)) { + if (!service.isSecureDeployment(deploymentName)) { return; } WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); @@ -74,14 +77,18 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); String deploymentName = deploymentUnit.getName(); - System.out.println(">>>>> deploymentName=" + deploymentName); KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); //log.info("********* CHECK KEYCLOAK DEPLOYMENT: " + deploymentName); - if (service.isKeycloakDeployment(deploymentName)) { - + if (service.isSecureDeployment(deploymentName)) { addKeycloakAuthData(phaseContext, deploymentName, service); } + if (service.isKeycloakServerDeployment(deploymentName)) { + final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION); + String webContext = service.getWebContext(deploymentName); + if (webContext == null) throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Auth Server"); + description.setModuleName(webContext); + } // FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK // todo notsure if we need this @@ -102,14 +109,6 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP warMetaData.setMergedJBossWebMetaData(webMetaData); } - if (service.isKeycloakServerDeployment(deploymentName)) { - final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION); - String webContext = service.getWebContext(deploymentName); - if (webContext == null) throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Auth Server"); - description.setModuleName(webContext); - return; - } - LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); if (loginConfig == null) { loginConfig = new LoginConfigMetaData(); diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java index ee91ef3152..762f05e2a1 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -51,11 +51,11 @@ public final class KeycloakAdapterConfigService implements Service realms = new HashMap(); - private Map deployments = new HashMap(); - // key=server deployment name; value=json - private Map serverDeployments = new HashMap(); - // key=server deployment name; value=web-context + // keycloak-secured deployments + private Map secureDeployments = new HashMap(); + + // key=auth-server deployment name; value=web-context private Map webContexts = new HashMap(); private KeycloakAdapterConfigService() { @@ -77,8 +77,8 @@ public final class KeycloakAdapterConfigService implements Service> newControllers) throws OperationFailedException { + protected boolean requiresRuntimeVerification() { + return false; } } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java index 685a910355..c7e7ae38db 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.descriptions.ResourceDescriptionResolver; import org.jboss.as.controller.operations.validation.ParameterValidator; import org.jboss.as.controller.registry.OperationEntry; import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; @@ -49,7 +50,7 @@ public class AuthServerDefinition extends SimpleResourceDefinition { new SimpleAttributeDefinitionBuilder("enabled", ModelType.BOOLEAN, true) .setXmlName("enabled") .setAllowExpression(true) - .setDefaultValue(new ModelNode(true)) + .setDefaultValue(new ModelNode(false)) .setRestartAllServices() .build(); @@ -62,18 +63,12 @@ public class AuthServerDefinition extends SimpleResourceDefinition { .setRestartAllServices() .build(); - /* protected static final SimpleAttributeDefinition KEYCLOAK_SERVER_JSON = - new SimpleAttributeDefinitionBuilder("keycloak-server-json", ModelType.STRING, true) - .setXmlName("keycloak-server-json") - .setAllowExpression(true) - .setRestartAllServices() - .build(); */ + protected static final ResourceDescriptionResolver rscDescriptionResolver = KeycloakExtension.getResourceDescriptionResolver(TAG_NAME); public static final List ALL_ATTRIBUTES = new ArrayList(); static { ALL_ATTRIBUTES.add(ENABLED); ALL_ATTRIBUTES.add(WEB_CONTEXT); - //ALL_ATTRIBUTES.add(KEYCLOAK_SERVER_JSON); } private static final Map DEFINITION_LOOKUP = new HashMap(); @@ -87,7 +82,7 @@ public class AuthServerDefinition extends SimpleResourceDefinition { public AuthServerDefinition() { super(PathElement.pathElement(TAG_NAME), - KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + rscDescriptionResolver, AuthServerAddHandler.INSTANCE, AuthServerRemoveHandler.INSTANCE, null, @@ -98,6 +93,7 @@ public class AuthServerDefinition extends SimpleResourceDefinition { public void registerOperations(ManagementResourceRegistration resourceRegistration) { super.registerOperations(resourceRegistration); resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + resourceRegistration.registerOperationHandler(ManageOverlayHandler.DEFINITION, ManageOverlayHandler.INSTANCE); } @Override diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java index 7f92ab2ae8..8b5dc5a298 100644 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java @@ -20,11 +20,16 @@ package org.keycloak.subsystem.extension.authserver; import org.jboss.as.controller.AbstractRemoveStepHandler; import org.jboss.as.controller.OperationContext; 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.operations.common.Util; import org.jboss.dmr.ModelNode; import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; +import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; /** * Remove an auth-server from a realm. @@ -38,17 +43,33 @@ public final class AuthServerRemoveHandler extends AbstractRemoveStepHandler { private AuthServerRemoveHandler() {} @Override - protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + protected void performRemove(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { //KeycloakAdapterConfigService.INSTANCE.removeAuthServer() System.out.println("*** performRuntime ** operation"); System.out.println(operation.toString()); System.out.println("*** performRuntime ** model"); System.out.println(model.toString()); - String deploymentName = Util.getNameFromAddress(operation.get(ADDRESS)); - System.out.println("*** authServerName=" + deploymentName); - if (!deploymentName.toLowerCase().endsWith(".war")) { - deploymentName += ".war"; - } + + String deploymentName = AuthServerUtil.getDeploymentName(operation); KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName); + + if (requiresRuntime(context)) { // don't do this on a domain controller + addStepToRemoveAuthServer(context, deploymentName); + } + + super.performRemove(context, operation, model); + } + + private void addStepToRemoveAuthServer(OperationContext context, String deploymentName) { + PathAddress deploymentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); + ModelNode op = Util.createOperation(REMOVE, deploymentAddress); + System.out.println("**** Removing deployment *****"); + System.out.println(op.toString()); + context.addStep(op, getRemoveHandler(context, deploymentAddress), OperationContext.Stage.MODEL); + } + + private OperationStepHandler getRemoveHandler(OperationContext context, PathAddress address) { + ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); + return rootResourceRegistration.getOperationHandler(address, REMOVE); } } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java index a7a4fd798d..30b170be25 100644 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java @@ -16,11 +16,10 @@ */ package org.keycloak.subsystem.extension.authserver; -import java.io.ByteArrayOutputStream; -import java.io.IOException; +import java.io.File; +import java.net.URISyntaxException; import java.net.URL; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; +import java.util.Iterator; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; @@ -29,9 +28,14 @@ import org.jboss.as.controller.PathElement; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PERSISTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REDEPLOY; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEPLOY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; @@ -43,6 +47,7 @@ import org.jboss.modules.Resource; import org.jboss.modules.filter.PathFilter; /** + * Utility methods that help assemble and start an auth server. * * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. */ @@ -50,119 +55,301 @@ public class AuthServerUtil { private static final ModuleIdentifier KEYCLOAK_SUBSYSTEM = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-subsystem"); - private static URL authServerUrl = null; + private final String authServerName; + private final PathAddress pathAddress; + private String deploymentName; - private static String defaultAuthServerJson = ""; + //private String overlayName; + private Module subsysModule; + private String keycloakVersion; - static String getDefaultAuthServerJson() { - if (authServerUrl == null) getWarUrl(); - return defaultAuthServerJson; + //private File overlaysDir; + private URL authServerUrl = null; + //private URL serverConfig = null; + //private Set spiUrls = new HashSet(); + + AuthServerUtil(ModelNode operation) { + this.authServerName = getAuthServerName(operation); + this.pathAddress = getPathAddress(operation); + this.deploymentName = getDeploymentName(operation); + + //this.overlayName = deploymentName + "-keycloak-overlay"; + setModule(); + findAuthServerUrl(); + //findSpiUrls(); + + System.out.println("&&&&& " + authServerName + " authServerUrl=" + authServerUrl); +// System.out.println("&&&&& " + authServerName + " spiUrls=" + spiUrls); +// System.out.println("&&&&& " + authServerName + " serverConfig=" + serverConfig); } - // Can return the URL, null, or throw IllegalStateException - // This also finds the defaultAuthServerJson and sets the instance var for it. - private static URL getWarUrl() throws IllegalStateException { - if (authServerUrl != null) { // only need to find this once - return authServerUrl; - } + String getDeploymentName() { + return this.deploymentName; + } - Module module; + private void setModule() { try { - module = Module.getModuleFromCallerModuleLoader(KEYCLOAK_AUTH_SERVER); + this.subsysModule = Module.getModuleFromCallerModuleLoader(KEYCLOAK_SUBSYSTEM); + this.keycloakVersion = subsysModule.getProperty("keycloak-version"); } catch (ModuleLoadException e) { - throw new IllegalStateException("Keycloak Auth Server not installed as a module.", e); + throw new IllegalStateException("Can't find Keycloak subsystem.", e); } + } - URL warUrl = null; + /*private void findSpiUrls() throws IllegalStateException { try { - java.util.Iterator rscIterator = module.iterateResources(new PathFilter() { + Iterator rscIterator = this.subsysModule.iterateResources(new PathFilter() { @Override public boolean accept(String string) { - return true; + return string.equals(AuthServerUtil.this.authServerName); } }); - // There should be only one war resource, the auth server while (rscIterator.hasNext()) { Resource rsc = rscIterator.next(); System.out.println("rsc.getName()=" + rsc.getName()); URL url = rsc.getURL(); - if (url.toExternalForm().toLowerCase().endsWith(".war")) { - warUrl = url; - setDefaultAuthServerJson(rsc); + + if (isJar(rsc)) { + this.spiUrls.add(url); + } + if (isServerConfig(rsc)) { + this.serverConfig = url; + } + } + } catch (ModuleLoadException e) { + throw new IllegalStateException(e); + } + }*/ + + private void findAuthServerUrl() throws IllegalStateException { + try { + Iterator rscIterator = this.subsysModule.iterateResources(new PathFilter() { + @Override + public boolean accept(String string) { + return string.equals(""); + } + }); + + while (rscIterator.hasNext()) { + Resource rsc = rscIterator.next(); + System.out.println("rsc.getName()=" + rsc.getName()); + URL url = rsc.getURL(); + String parent = ""; + try { + parent = new File(url.toURI()).getParent(); + } catch (URISyntaxException e) { + continue; + } catch (IllegalArgumentException e) { + continue; + } + + if (isAuthServer(rsc, parent)) { + this.authServerUrl = url; + //File mainDir = new File(parent).getParentFile(); + //this.overlaysDir = new File(mainDir, "overlays"); break; } } } catch (ModuleLoadException e) { throw new IllegalStateException(e); - } catch (IOException e) { - throw new IllegalStateException(e); } - - authServerUrl = warUrl; - System.out.println("&&&&& authServerUrl=" + authServerUrl); - return authServerUrl; } - // return deploymentName this will be started under - static String addStepToStartAuthServer(OperationContext context, ModelNode operation) throws OperationFailedException { + private boolean isAuthServer(Resource rsc, String parent) { + return rsc.getName().equals("keycloak-server-" + keycloakVersion + ".war") + && parent.toLowerCase().endsWith("auth-server"); + } - PathAddress authServerAddr = PathAddress.pathAddress(operation.get(ADDRESS)); - String deploymentName = authServerAddr.getElement(1).getValue(); + /*private boolean isServerConfig(Resource rsc) { + return rsc.getName().endsWith("/keycloak-server.json"); + } + + private boolean isJar(Resource rsc) { + return rsc.getName().toLowerCase().endsWith(".jar"); + } + + boolean serverOverlayDirExists() { + return new File(overlaysDir, authServerName).exists(); + } + + private boolean hasOverlays() { + return (this.serverConfig != null) || (!this.spiUrls.isEmpty()); + }*/ + + void addStepToUploadAuthServer(OperationContext context, boolean isEnabled) throws OperationFailedException { + PathAddress deploymentAddress = deploymentAddress(); + ModelNode op = Util.createOperation(ADD, deploymentAddress); + op.get(ENABLED).set(isEnabled); + op.get(PERSISTENT).set(false); // prevents writing this deployment out to standalone.xml + + if (authServerUrl == null) { + throw new OperationFailedException("Keycloak Auth Server WAR not found in keycloak-wildfly-subsystem module"); + } + + String urlString = authServerUrl.toExternalForm(); + ModelNode contentItem = new ModelNode(); + contentItem.get(URL).set(urlString); + op.get(CONTENT).add(contentItem); + + System.out.println("*** add auth server operation"); + System.out.println(op.toString()); + context.addStep(op, getHandler(context, deploymentAddress, ADD), OperationContext.Stage.MODEL); + + /*File authServerOverlaysDir = new File(this.overlaysDir, authServerName); + System.out.println("authServerOverlaysDir" + authServerOverlaysDir.getAbsolutePath()); + if (!authServerOverlaysDir.exists()) { + authServerOverlaysDir.mkdir(); + addOverlay(context); + linkToDeployment(context); + }*/ + } + + void addStepToRedeployAuthServer(OperationContext context) { + addDeploymentAction(context, REDEPLOY); + } + + void addStepToUndeployAuthServer(OperationContext context) { + addDeploymentAction(context, UNDEPLOY); + } + + void addStepToDeployAuthServer(OperationContext context) { + addDeploymentAction(context, DEPLOY); + } + + private void addDeploymentAction(OperationContext context, String operation) { + PathAddress deploymentAddress = deploymentAddress(); + ModelNode op = Util.createOperation(operation, deploymentAddress); + op.get(RUNTIME_NAME).set(deploymentName); + System.out.println(">>>> operation=" + operation); + System.out.println(op.toString()); + context.addStep(op, getHandler(context, deploymentAddress, operation), OperationContext.Stage.MODEL); + } + + private PathAddress deploymentAddress() { + return PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); + } + + /*void addStepsToAssembleOverlay(OperationContext context) throws OperationFailedException { + if (hasOverlays()) { + addOverlay(context); + addKeycloakServerJson(context); + addSpiJars(context); + linkToDeployment(context); + } + removeOverlayDir(); + } + + private void removeOverlayDir() { + // TODO implement as operation + } + + private void addOverlay(OperationContext context) throws OperationFailedException { + if (!hasOverlays()) return; + + PathAddress overlayAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName)); + + ModelNode addOp = Util.createOperation(ADD, overlayAddress); + //addOp.get(PERSISTENT).set(false); + + addRollbackFalse(addOp); + System.out.println("*** add overlay operation"); + System.out.println(addOp.toString()); + context.addStep(addOp, getAddHandler(context, overlayAddress), OperationContext.Stage.MODEL); + } + + private void addKeycloakServerJson(OperationContext context) throws OperationFailedException { + if (this.serverConfig == null) { + return; + } + + addOveralyContent(context, this.serverConfig, "/WEB-INF/classes/META-INF/keycloak-server.json"); + addChangeToOperation(context, this.serverConfig, ManageOverlayHandler.changeToEnum.deployed); + } + + private void addSpiJars(OperationContext context) throws OperationFailedException { + if (this.spiUrls.isEmpty()) { + return; + } + + for (URL source : this.spiUrls) { + try { + String fileName = new java.io.File(source.toURI()).getName(); + addOveralyContent(context, source, "/WEB-INF/lib/" + fileName); + } catch (URISyntaxException e) { + throw new OperationFailedException(e); + } catch (IllegalArgumentException e) { + throw new OperationFailedException(e); + } + } + } + + private void linkToDeployment(OperationContext context) throws OperationFailedException { + if (!hasOverlays()) return; + + PathAddress linkAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName), + PathElement.pathElement(DEPLOYMENT, deploymentName)); + ModelNode op = Util.createOperation(ADD, linkAddress); + + addRollbackFalse(op); + System.out.println("*** link to deployment operation"); + System.out.println(op.toString()); + context.addStep(op, getAddHandler(context, linkAddress), OperationContext.Stage.MODEL); + } + + private void addOveralyContent(OperationContext context, URL source, String destination) throws OperationFailedException { + PathAddress contentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName), + PathElement.pathElement(CONTENT, destination)); + ModelNode op = Util.createOperation(ADD, contentAddress); + + ModelNode contentItem = new ModelNode(); + contentItem.get(URL).set(source.toExternalForm()); + op.get(CONTENT).set(contentItem); + + addRollbackFalse(op); + System.out.println("*** add content operation"); + System.out.println(op.toString()); + + context.addStep(op, getAddHandler(context, contentAddress), OperationContext.Stage.MODEL); + } + + private void addChangeToOperation(OperationContext context, URL source, ManageOverlayHandler.changeToEnum changeTo) { + ModelNode op = Util.createOperation(ManageOverlayHandler.OP, this.pathAddress); + op.get(ManageOverlayHandler.URL.getName()).set(source.toExternalForm()); + op.get(ManageOverlayHandler.CHANGE_TO.getName()).set(changeTo.toString()); + + System.out.println("************change-to operation********************"); + System.out.println(op.toString()); + context.addStep(op, ManageOverlayHandler.INSTANCE, OperationContext.Stage.RUNTIME, false); + }*/ + + private OperationStepHandler getHandler(OperationContext context, PathAddress address, String opName) { + ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); + return rootResourceRegistration.getOperationHandler(address, opName); + //return new IgnoreIfResourceExistsHandler(handler); + } + + private void addRollbackFalse(ModelNode modelNode) { + modelNode.get(ROLLBACK_ON_RUNTIME_FAILURE).set(false); + } + + static String getDeploymentName(ModelNode operation) { + String deploymentName = Util.getNameFromAddress(operation.get(ADDRESS)); + System.out.println("*** authServerName=" + deploymentName); if (!deploymentName.toLowerCase().endsWith(".war")) { deploymentName += ".war"; } - PathAddress deploymentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); - ModelNode op = Util.createOperation(ADD, deploymentAddress); - op.get(ENABLED).set(true); - op.get(PERSISTENT).set(false); // prevents writing this deployment out to standalone.xml - - URL warUrl = null; - try { - warUrl = getWarUrl(); - } catch (IllegalStateException e) { - throw new OperationFailedException(e); - } - - if (warUrl == null) { - throw new OperationFailedException("Keycloak Auth Server WAR not found in keycloak-auth-server module"); - } - - String urlString = warUrl.toExternalForm(); - System.out.println(warUrl); - ModelNode contentItem = new ModelNode(); - contentItem.get(URL).set(urlString); - op.get(CONTENT).add(contentItem); - System.out.println("****** operation ************"); - System.out.println(op.toString()); - ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); - OperationStepHandler handler = rootResourceRegistration.getOperationHandler(deploymentAddress, ADD); - context.addStep(op, handler, OperationContext.Stage.MODEL); - return deploymentName; } - private static void setDefaultAuthServerJson(Resource rsc) throws IOException { - JarInputStream jarStream = null; - try { - jarStream = new JarInputStream(rsc.openStream()); - JarEntry je; - while ((je = jarStream.getNextJarEntry()) != null) { - if (!je.getName().equals("WEB-INF/classes/META-INF/keycloak-server.json")) continue; - - int len = 0; - byte[] buffer = new byte[1024]; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - while ((len = jarStream.read(buffer)) != -1) { - baos.write(buffer, 0, len); - } - - defaultAuthServerJson = baos.toString(); - return; - } - } finally { - jarStream.close(); - } + static String getAuthServerName(ModelNode operation) { + PathAddress pathAddr = getPathAddress(operation); + return pathAddr.getElement(pathAddr.size() - 1).getValue(); } + + static PathAddress getPathAddress(ModelNode operation) { + return PathAddress.pathAddress(operation.get(ADDRESS)); + } + } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java index b7e53ea753..11cedce525 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java @@ -21,14 +21,19 @@ import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.SimpleAttributeDefinition; import java.util.List; -import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; +import org.jboss.as.controller.ModelOnlyWriteAttributeHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.registry.Resource; +import org.jboss.dmr.ModelNode; +import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; /** * Update an attribute on an Auth Server. * * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. */ -public class AuthServerWriteAttributeHandler extends ReloadRequiredWriteAttributeHandler { +public class AuthServerWriteAttributeHandler extends ModelOnlyWriteAttributeHandler { //extends ReloadRequiredWriteAttributeHandler { public AuthServerWriteAttributeHandler(List definitions) { this(definitions.toArray(new AttributeDefinition[definitions.size()])); @@ -38,4 +43,56 @@ public class AuthServerWriteAttributeHandler extends ReloadRequiredWriteAttribut super(definitions); } + @Override + protected void finishModelStage(OperationContext context, ModelNode operation, String attributeName, ModelNode newValue, ModelNode oldValue, Resource model) throws OperationFailedException { + if (!context.isNormalServer() || attribNotChanging(attributeName, newValue, oldValue)) { + super.finishModelStage(context, operation, attributeName, newValue, oldValue, model); + return; + } + + System.out.println("**** finishModelStage *****"); + System.out.println("** operation **"); + System.out.println(operation.toString()); + System.out.println("** attributeName=" + attributeName); + System.out.println("** oldValue=" + oldValue); + System.out.println("** newValue=" + newValue); + + AuthServerUtil authServerUtil = new AuthServerUtil(operation); + boolean isEnabled = isEnabled(model); // is server currently enabled? + + if (attributeName.equals(AuthServerDefinition.WEB_CONTEXT.getName())) { + String deploymentName = AuthServerUtil.getDeploymentName(operation); + KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName); + KeycloakAdapterConfigService.INSTANCE.addServerDeployment(deploymentName, newValue.asString()); + if (isEnabled) { + authServerUtil.addStepToRedeployAuthServer(context); + } + } + + if (attributeName.equals(AuthServerDefinition.ENABLED.getName())) { + if (!isEnabled) { // we are disabling + authServerUtil.addStepToUndeployAuthServer(context); + } else { // we are enabling + authServerUtil.addStepToDeployAuthServer(context); + } + } + + super.finishModelStage(context, operation, attributeName, newValue, oldValue, model); + } + + // Is auth server currently enabled? + private boolean isEnabled(Resource model) { + ModelNode authServer = model.getModel(); + ModelNode isEnabled = authServer.get(AuthServerDefinition.ENABLED.getName()); + if (!isEnabled.isDefined()) isEnabled = AuthServerDefinition.ENABLED.getDefaultValue(); + return isEnabled.asBoolean(); + } + + private boolean attribNotChanging(String attributeName, ModelNode newValue, ModelNode oldValue) { + SimpleAttributeDefinition attribDef = AuthServerDefinition.lookup(attributeName); + if (!oldValue.isDefined()) oldValue = attribDef.getDefaultValue(); + if (!newValue.isDefined()) newValue = attribDef.getDefaultValue(); + return newValue.equals(oldValue); + } + } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java new file mode 100755 index 0000000000..d347071495 --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +import java.util.List; +import java.util.Set; +import org.jboss.as.controller.AbstractRuntimeOnlyHandler; +import org.jboss.as.controller.OperationDefinition; +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.SimpleOperationDefinitionBuilder; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.as.controller.operations.validation.EnumValidator; +import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; +import org.jboss.as.controller.registry.Resource; +import org.jboss.dmr.ModelType; +import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; + +/** + * Rename the extension of an overlay in the overlays/ directory. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public final class ManageOverlayHandler extends AbstractRuntimeOnlyHandler { + + public static final String OP = "change-to"; + + public static ManageOverlayHandler INSTANCE = new ManageOverlayHandler(); + + public enum changeToEnum {deployed, undeployed}; + + protected static final SimpleAttributeDefinition URL = + new SimpleAttributeDefinitionBuilder("url", ModelType.STRING, false) + .setAllowExpression(false) + .build(); + + protected static final SimpleAttributeDefinition CHANGE_TO = + new SimpleAttributeDefinitionBuilder(OP, ModelType.STRING, false) + .setAllowExpression(false) + .setValidator(new EnumValidator(changeToEnum.class, false, false)) + .build(); + + public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder("manage-overlay", AuthServerDefinition.rscDescriptionResolver) + .addParameter(URL) + .addParameter(CHANGE_TO) + .build(); + + private ManageOverlayHandler() { + } + + @Override + protected void executeRuntimeStep(OperationContext context, ModelNode mn) throws OperationFailedException { + System.out.println("Executing!!!!"); + PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, "main-auth-server.war-keycloak-overlay")); + //PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement("path", "user.dir")); + ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); + + Resource resource = context.readResourceFromRoot(pathAddress); + Set childAddrs = rootResourceRegistration.getChildAddresses(pathAddress); + Set children = rootResourceRegistration.getChildNames(pathAddress); + + + System.out.println("***************"); + System.out.println("childAddrs=" + childAddrs); + System.out.println("children=" + children); + System.out.println("model=" + resource.getModel()); + System.out.println("children=" + resource.getChildrenNames("deployment")); + context.completeStep(OperationContext.ResultHandler.NOOP_RESULT_HANDLER); + } + +} diff --git a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties index 533234b85d..45c4c6e22c 100755 --- a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties +++ b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -9,8 +9,10 @@ keycloak.subsystem.secure-deployment=A deployment secured by Keycloak. keycloak.auth-server=A Keycloak Auth Server keycloak.auth-server.add=Add an Auth Server to the subsystem. keycloak.auth-server.remove=Remove an Auth Server from the subsystem. +keycloak.auth-server.manage-overlay=Internal use only. Do not call from CLI!! +keycloak.auth-server.manage-overlay.url=Internal use only. Do not call from CLI!! The overlay URL that needs its extension to be changed. +keycloak.auth-server.manage-overlay.change-to=Internal use only. Do not call from CLI!! The extension the overlay will be changed to. keycloak.auth-server.enabled=Enable or disable the Auth Server. -keycloak.auth-server.keycloak-server-json=Externalized version of keycloak-server.json keycloak.auth-server.web-context=Web context the auth-server will use. Also, the module name of the auth-server deployment. keycloak.realm=A Keycloak realm. diff --git a/pom.xml b/pom.xml index 80d627c49a..63187437f3 100755 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ 1.5.10 7.1.1.Final 8.1.0.Final - 1.0.0.Alpha5 + 1.0.0.Alpha9 1.0.2.Final 2.2 1.14.1-beta @@ -346,24 +346,24 @@ ${twitter4j.version} - + - com.google.zxing - core - ${google.zxing.version} - + com.google.zxing + core + ${google.zxing.version} + - com.google.zxing - javase - ${google.zxing.version} - + com.google.zxing + javase + ${google.zxing.version} + - - - com.icegreen - greenmail - 1.3.1b - + + + com.icegreen + greenmail + 1.3.1b + @@ -372,18 +372,18 @@ ${winzipaes.version} - - - org.seleniumhq.selenium - selenium-java - ${selenium.version} - - - org.seleniumhq.selenium - selenium-chrome-driver - ${selenium.version} + + + org.seleniumhq.selenium + selenium-java + ${selenium.version} + + + org.seleniumhq.selenium + selenium-chrome-driver + ${selenium.version} test - + org.mongodb mongo-java-driver @@ -423,12 +423,12 @@ ${mysql.version} + + org.apache.httpcomponents + httpclient + ${keycloak.apache.httpcomponents.version} + + --> org.wildfly.core wildfly-controller @@ -451,6 +451,18 @@ pom test + + org.wildfly.core + wildfly-core-feature-pack + pom + ${wildfly.core.version} + + + org.wildfly.core + wildfly-core-feature-pack + zip + ${wildfly.core.version} + org.wildfly wildfly-undertow @@ -597,14 +609,14 @@ maven-deploy-plugin 2.5 - - org.apache.maven.plugins - maven-war-plugin - 2.3 - - false - - + + org.apache.maven.plugins + maven-war-plugin + 2.3 + + false + + com.lazerycode.jmeter jmeter-maven-plugin diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 89e5d40d7c..a2d5a60d29 100755 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -41,7 +41,6 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; -import org.keycloak.adapters.AdapterConstants; /** * @author Bill Burke @@ -58,7 +57,7 @@ public class KeycloakApplication extends Application { protected String contextPath; public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { - loadConfig(context); + loadConfig(); this.sessionFactory = createSessionFactory(); @@ -103,26 +102,6 @@ public class KeycloakApplication extends Application { return uriInfo.getBaseUriBuilder().replacePath(getContextPath()).build(); } - private static void loadConfig(ServletContext context) { - String json = context.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); - if (json == null) { - loadConfig(); // from file - } else { - loadConfig(json); // from ServletContext/Keycloak subsystem - } - } - - private static void loadConfig(String json) { - try { - JsonNode node = new ObjectMapper().readTree(json); - Config.init(new JsonConfigProvider(node)); - } catch (IOException e) { - throw new RuntimeException("Failed to load config", e); - } - - log.info("Loaded config from Keycloak subsystem"); - } - public static void loadConfig() { try { URL config = null; From 3f75ebf029af94103fb077a77e84902c8eddb605 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 28 Oct 2014 11:27:49 -0400 Subject: [PATCH 3/6] KEYCLOAK-795 Move Auth Server into Keycloak Subsystem --- distribution/appliance-dist/assembly.xml | 2 +- .../main/module.xml | 6 +- integration/keycloak-feature-pack/pom.xml | 83 ------ .../keycloak-adapter-core/main/module.xml | 39 --- .../keycloak/keycloak-core/main/module.xml | 38 --- .../main/module.xml | 38 --- .../keycloak-undertow-adapter/main/module.xml | 44 --- .../keycloak-wildfly-adapter/main/module.xml | 46 ---- .../main/module.xml | 47 ---- integration/pom.xml | 1 - .../KeycloakAdapterConfigService.java | 1 - .../KeycloakDependencyProcessor.java | 3 - .../extension/SecureDeploymentDefinition.java | 1 - .../authserver/AbstractAddOverlayHandler.java | 182 +++++++++++++ .../authserver/AddProviderHandler.java | 34 ++- .../authserver/AuthServerAddHandler.java | 8 - .../authserver/AuthServerDefinition.java | 3 +- .../authserver/AuthServerRemoveHandler.java | 8 - .../extension/authserver/AuthServerUtil.java | 251 +++--------------- .../AuthServerWriteAttributeHandler.java | 7 - .../authserver/ManageOverlayHandler.java | 99 ------- .../OverlayKeycloakServerJsonHandler.java | 43 +++ .../extension/LocalDescriptions.properties | 9 +- 23 files changed, 307 insertions(+), 686 deletions(-) delete mode 100644 integration/keycloak-feature-pack/pom.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml delete mode 100644 integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml create mode 100644 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AbstractAddOverlayHandler.java delete mode 100755 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java create mode 100644 integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/OverlayKeycloakServerJsonHandler.java diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml index acaa91fa78..0e788d8c34 100755 --- a/distribution/appliance-dist/assembly.xml +++ b/distribution/appliance-dist/assembly.xml @@ -48,7 +48,7 @@ ${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF - keycloak/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/overlays + keycloak/standalone/configuration keycloak-server.json diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml index 476147922a..636909dd99 100755 --- a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml +++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml @@ -2,7 +2,7 @@ diff --git a/integration/keycloak-feature-pack/pom.xml b/integration/keycloak-feature-pack/pom.xml deleted file mode 100644 index b2560302b8..0000000000 --- a/integration/keycloak-feature-pack/pom.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - 4.0.0 - - - org.keycloak - keycloak-parent - 1.1.0-Alpha1-SNAPSHOT - ../../pom.xml - - - keycloak-feature-pack - Keycloak Feature Pack - - jar - - - - - - org.wildfly.core - wildfly-core-feature-pack - zip - - - * - * - - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${maven.compiler.source} - ${maven.compiler.target} - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.8.1 - - false - true - -Xmx512m - - - jboss.home - ${jboss.home} - - - - **/*TestCase.java - - once - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml deleted file mode 100644 index 0def6052f1..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-core/main/module.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml deleted file mode 100644 index da3e18b810..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-core/main/module.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml deleted file mode 100644 index 89b0a6de2e..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-jboss-adapter-core/main/module.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml deleted file mode 100644 index dedbd945ec..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-undertow-adapter/main/module.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml deleted file mode 100644 index ceb9b4bf89..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-adapter/main/module.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml b/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml deleted file mode 100644 index 9ccd15f752..0000000000 --- a/integration/keycloak-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wildfly-subsystem/main/module.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/integration/pom.xml b/integration/pom.xml index 06997d2090..0c52175399 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -28,6 +28,5 @@ js installed admin-client - keycloak-feature-pack diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java index 762f05e2a1..868f5858d0 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -78,7 +78,6 @@ public final class KeycloakAdapterConfigService implements Service getServerGroupNames(OperationContext context) { + return context.readResourceFromRoot(PathAddress.EMPTY_ADDRESS).getChildrenNames("server-group"); + } + + private void addContent(OperationContext context, PathAddress overlayAddress, byte[] bytes, String overlayPath) throws OperationFailedException { + PathAddress contentAddress = overlayAddress.append("content", overlayPath); + ModelNode op = Util.createAddOperation(contentAddress); + + ModelNode content = new ModelNode(); + content.get("bytes").set(bytes); + op.get("content").set(content); + + doAddStep(context, contentAddress, op); + } + + private void doAddStep(OperationContext context, PathAddress address, ModelNode operation) { + //System.out.println("**** Adding Add Step ****"); + //System.out.println(scrub(operation).toString()); + context.addStep(operation, getHandler(context, address, ADD), OperationContext.Stage.MODEL); + } + + // used for debugging + private ModelNode scrub(ModelNode op) { + ModelNode scrubbed = op.clone(); + if (scrubbed.has("content")) { + scrubbed.get("content").set("BYTES REMOVED FOR DISPLAY"); + } + if (scrubbed.has("bytes-to-upload")) { + scrubbed.get("bytes-to-upload").set("BYTES REMOVED FOR DISPLAY"); + } + return scrubbed; + } + + /** + * Get the WAR path where the overlay will live. + * + * @param file The name of the file being uploaded. + * @return The overlay path as a String. + */ + abstract String getOverlayPath(String fileName); +} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AddProviderHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AddProviderHandler.java index 4d632723e1..f661891b89 100644 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AddProviderHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AddProviderHandler.java @@ -17,19 +17,43 @@ package org.keycloak.subsystem.extension.authserver; -import org.jboss.as.controller.AbstractModelUpdateHandler; -import org.jboss.as.controller.OperationFailedException; +import java.io.File; +import org.jboss.as.controller.OperationDefinition; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleOperationDefinitionBuilder; import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; /** + * Operation to add a provider jar to WEB-INF/lib. * * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. */ -public class AddProviderHandler extends AbstractModelUpdateHandler { +public class AddProviderHandler extends AbstractAddOverlayHandler { + + public static final String OP = "add-provider"; + + public static final AddProviderHandler INSTANCE = new AddProviderHandler(); + + protected static final SimpleAttributeDefinition UPLOADED_FILE_NAME = + new SimpleAttributeDefinitionBuilder(UPLOADED_FILE_OP_NAME, ModelType.STRING, false) + .setAllowExpression(false) + .setAllowNull(false) + .setDefaultValue(new ModelNode().set("myprovider.jar")) + .build(); + + public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OP, AuthServerDefinition.rscDescriptionResolver) + .addParameter(BYTES_TO_UPLOAD) + .addParameter(UPLOADED_FILE_NAME) + .build(); @Override - protected void updateModel(ModelNode operation, ModelNode model) throws OperationFailedException { - + String getOverlayPath(String fileName) { + if (!fileName.toLowerCase().endsWith(".jar")) { + throw new IllegalArgumentException("Uploaded file name must end with .jar"); + } + return "/WEB-INF/lib/" + fileName; } } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerAddHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerAddHandler.java index 23ade1e9f0..fd3fcddda5 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerAddHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerAddHandler.java @@ -52,14 +52,6 @@ public final class AuthServerAddHandler extends AbstractAddStepHandler { attr.validateAndSet(operation, model); } - System.out.println("**************************"); - System.out.println("operation"); - System.out.println(operation.toString()); - System.out.println("**************************"); - System.out.println("model"); - System.out.println(model.toString()); - System.out.println("**************************"); - // returns early if on domain controller if (!requiresRuntime(context)) return; diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java index c7e7ae38db..ff43a6f71c 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerDefinition.java @@ -93,7 +93,8 @@ public class AuthServerDefinition extends SimpleResourceDefinition { public void registerOperations(ManagementResourceRegistration resourceRegistration) { super.registerOperations(resourceRegistration); resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); - resourceRegistration.registerOperationHandler(ManageOverlayHandler.DEFINITION, ManageOverlayHandler.INSTANCE); + resourceRegistration.registerOperationHandler(AddProviderHandler.DEFINITION, AddProviderHandler.INSTANCE); + resourceRegistration.registerOperationHandler(OverlayKeycloakServerJsonHandler.DEFINITION, OverlayKeycloakServerJsonHandler.INSTANCE); } @Override diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java index 8b5dc5a298..f84e45ffe1 100644 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerRemoveHandler.java @@ -44,12 +44,6 @@ public final class AuthServerRemoveHandler extends AbstractRemoveStepHandler { @Override protected void performRemove(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { - //KeycloakAdapterConfigService.INSTANCE.removeAuthServer() - System.out.println("*** performRuntime ** operation"); - System.out.println(operation.toString()); - System.out.println("*** performRuntime ** model"); - System.out.println(model.toString()); - String deploymentName = AuthServerUtil.getDeploymentName(operation); KeycloakAdapterConfigService.INSTANCE.removeServerDeployment(deploymentName); @@ -63,8 +57,6 @@ public final class AuthServerRemoveHandler extends AbstractRemoveStepHandler { private void addStepToRemoveAuthServer(OperationContext context, String deploymentName) { PathAddress deploymentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); ModelNode op = Util.createOperation(REMOVE, deploymentAddress); - System.out.println("**** Removing deployment *****"); - System.out.println(op.toString()); context.addStep(op, getRemoveHandler(context, deploymentAddress), OperationContext.Stage.MODEL); } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java index 30b170be25..d4085c998c 100644 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerUtil.java @@ -17,9 +17,9 @@ package org.keycloak.subsystem.extension.authserver; import java.io.File; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.Iterator; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; @@ -27,13 +27,14 @@ import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ARCHIVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CONTENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ENABLED; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PERSISTENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PATH; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REDEPLOY; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RUNTIME_NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEPLOY; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.URL; @@ -43,8 +44,6 @@ import org.jboss.dmr.ModelNode; import org.jboss.modules.Module; import org.jboss.modules.ModuleIdentifier; import org.jboss.modules.ModuleLoadException; -import org.jboss.modules.Resource; -import org.jboss.modules.filter.PathFilter; /** * Utility methods that help assemble and start an auth server. @@ -57,14 +56,15 @@ public class AuthServerUtil { private final String authServerName; private final PathAddress pathAddress; - private String deploymentName; + private final String deploymentName; //private String overlayName; - private Module subsysModule; - private String keycloakVersion; + private final Module subsysModule; + private final String keycloakVersion; + private final boolean isAuthServerExploded; //private File overlaysDir; - private URL authServerUrl = null; + private final URI authServerUri; //private URL serverConfig = null; //private Set spiUrls = new HashSet(); @@ -72,137 +72,69 @@ public class AuthServerUtil { this.authServerName = getAuthServerName(operation); this.pathAddress = getPathAddress(operation); this.deploymentName = getDeploymentName(operation); - - //this.overlayName = deploymentName + "-keycloak-overlay"; - setModule(); - findAuthServerUrl(); - //findSpiUrls(); - - System.out.println("&&&&& " + authServerName + " authServerUrl=" + authServerUrl); -// System.out.println("&&&&& " + authServerName + " spiUrls=" + spiUrls); -// System.out.println("&&&&& " + authServerName + " serverConfig=" + serverConfig); + this.subsysModule = findSubsysModule(); + this.keycloakVersion = subsysModule.getProperty("keycloak-version"); + this.isAuthServerExploded = Boolean.parseBoolean(subsysModule.getProperty("auth-server-exploded")); + this.authServerUri = findAuthServerUri(); } String getDeploymentName() { return this.deploymentName; } - private void setModule() { + private Module findSubsysModule() { try { - this.subsysModule = Module.getModuleFromCallerModuleLoader(KEYCLOAK_SUBSYSTEM); - this.keycloakVersion = subsysModule.getProperty("keycloak-version"); + return Module.getModuleFromCallerModuleLoader(KEYCLOAK_SUBSYSTEM); } catch (ModuleLoadException e) { throw new IllegalStateException("Can't find Keycloak subsystem.", e); } } - /*private void findSpiUrls() throws IllegalStateException { + private URI findAuthServerUri() throws IllegalStateException { try { - Iterator rscIterator = this.subsysModule.iterateResources(new PathFilter() { - @Override - public boolean accept(String string) { - return string.equals(AuthServerUtil.this.authServerName); - } - }); - - while (rscIterator.hasNext()) { - Resource rsc = rscIterator.next(); - System.out.println("rsc.getName()=" + rsc.getName()); - URL url = rsc.getURL(); - - if (isJar(rsc)) { - this.spiUrls.add(url); - } - if (isServerConfig(rsc)) { - this.serverConfig = url; - } + URL subsysJar = this.subsysModule.getExportedResource("keycloak-wildfly-subsystem-" + this.keycloakVersion + ".jar"); + File subsysDir = new File(subsysJar.toURI()).getParentFile(); + File authServerDir = new File(subsysDir, "auth-server"); + if (this.isAuthServerExploded) { + return authServerDir.toURI(); + } else { + return new File(authServerDir, "keycloak-server-" + keycloakVersion + ".war").toURI(); } - } catch (ModuleLoadException e) { + } catch (URISyntaxException e) { throw new IllegalStateException(e); - } - }*/ - - private void findAuthServerUrl() throws IllegalStateException { - try { - Iterator rscIterator = this.subsysModule.iterateResources(new PathFilter() { - @Override - public boolean accept(String string) { - return string.equals(""); - } - }); - - while (rscIterator.hasNext()) { - Resource rsc = rscIterator.next(); - System.out.println("rsc.getName()=" + rsc.getName()); - URL url = rsc.getURL(); - String parent = ""; - try { - parent = new File(url.toURI()).getParent(); - } catch (URISyntaxException e) { - continue; - } catch (IllegalArgumentException e) { - continue; - } - - if (isAuthServer(rsc, parent)) { - this.authServerUrl = url; - //File mainDir = new File(parent).getParentFile(); - //this.overlaysDir = new File(mainDir, "overlays"); - break; - } - } - } catch (ModuleLoadException e) { + } catch (IllegalArgumentException e) { throw new IllegalStateException(e); } } - private boolean isAuthServer(Resource rsc, String parent) { - return rsc.getName().equals("keycloak-server-" + keycloakVersion + ".war") - && parent.toLowerCase().endsWith("auth-server"); - } - - /*private boolean isServerConfig(Resource rsc) { - return rsc.getName().endsWith("/keycloak-server.json"); - } - - private boolean isJar(Resource rsc) { - return rsc.getName().toLowerCase().endsWith(".jar"); - } - - boolean serverOverlayDirExists() { - return new File(overlaysDir, authServerName).exists(); - } - - private boolean hasOverlays() { - return (this.serverConfig != null) || (!this.spiUrls.isEmpty()); - }*/ - void addStepToUploadAuthServer(OperationContext context, boolean isEnabled) throws OperationFailedException { PathAddress deploymentAddress = deploymentAddress(); ModelNode op = Util.createOperation(ADD, deploymentAddress); op.get(ENABLED).set(isEnabled); op.get(PERSISTENT).set(false); // prevents writing this deployment out to standalone.xml - if (authServerUrl == null) { + if (authServerUri == null) { throw new OperationFailedException("Keycloak Auth Server WAR not found in keycloak-wildfly-subsystem module"); } - String urlString = authServerUrl.toExternalForm(); - ModelNode contentItem = new ModelNode(); - contentItem.get(URL).set(urlString); - op.get(CONTENT).add(contentItem); + op.get(CONTENT).add(makeContentItem()); - System.out.println("*** add auth server operation"); - System.out.println(op.toString()); context.addStep(op, getHandler(context, deploymentAddress, ADD), OperationContext.Stage.MODEL); + } - /*File authServerOverlaysDir = new File(this.overlaysDir, authServerName); - System.out.println("authServerOverlaysDir" + authServerOverlaysDir.getAbsolutePath()); - if (!authServerOverlaysDir.exists()) { - authServerOverlaysDir.mkdir(); - addOverlay(context); - linkToDeployment(context); - }*/ + private ModelNode makeContentItem() throws OperationFailedException { + ModelNode contentItem = new ModelNode(); + + if (this.isAuthServerExploded) { + String urlString = new File(authServerUri).getAbsolutePath(); + contentItem.get(PATH).set(urlString); + contentItem.get(ARCHIVE).set(false); + } else { + String urlString = authServerUri.toString(); + contentItem.get(URL).set(urlString); + } + + return contentItem; } void addStepToRedeployAuthServer(OperationContext context) { @@ -221,8 +153,6 @@ public class AuthServerUtil { PathAddress deploymentAddress = deploymentAddress(); ModelNode op = Util.createOperation(operation, deploymentAddress); op.get(RUNTIME_NAME).set(deploymentName); - System.out.println(">>>> operation=" + operation); - System.out.println(op.toString()); context.addStep(op, getHandler(context, deploymentAddress, operation), OperationContext.Stage.MODEL); } @@ -230,112 +160,13 @@ public class AuthServerUtil { return PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT, deploymentName)); } - /*void addStepsToAssembleOverlay(OperationContext context) throws OperationFailedException { - if (hasOverlays()) { - addOverlay(context); - addKeycloakServerJson(context); - addSpiJars(context); - linkToDeployment(context); - } - removeOverlayDir(); - } - - private void removeOverlayDir() { - // TODO implement as operation - } - - private void addOverlay(OperationContext context) throws OperationFailedException { - if (!hasOverlays()) return; - - PathAddress overlayAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName)); - - ModelNode addOp = Util.createOperation(ADD, overlayAddress); - //addOp.get(PERSISTENT).set(false); - - addRollbackFalse(addOp); - System.out.println("*** add overlay operation"); - System.out.println(addOp.toString()); - context.addStep(addOp, getAddHandler(context, overlayAddress), OperationContext.Stage.MODEL); - } - - private void addKeycloakServerJson(OperationContext context) throws OperationFailedException { - if (this.serverConfig == null) { - return; - } - - addOveralyContent(context, this.serverConfig, "/WEB-INF/classes/META-INF/keycloak-server.json"); - addChangeToOperation(context, this.serverConfig, ManageOverlayHandler.changeToEnum.deployed); - } - - private void addSpiJars(OperationContext context) throws OperationFailedException { - if (this.spiUrls.isEmpty()) { - return; - } - - for (URL source : this.spiUrls) { - try { - String fileName = new java.io.File(source.toURI()).getName(); - addOveralyContent(context, source, "/WEB-INF/lib/" + fileName); - } catch (URISyntaxException e) { - throw new OperationFailedException(e); - } catch (IllegalArgumentException e) { - throw new OperationFailedException(e); - } - } - } - - private void linkToDeployment(OperationContext context) throws OperationFailedException { - if (!hasOverlays()) return; - - PathAddress linkAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName), - PathElement.pathElement(DEPLOYMENT, deploymentName)); - ModelNode op = Util.createOperation(ADD, linkAddress); - - addRollbackFalse(op); - System.out.println("*** link to deployment operation"); - System.out.println(op.toString()); - context.addStep(op, getAddHandler(context, linkAddress), OperationContext.Stage.MODEL); - } - - private void addOveralyContent(OperationContext context, URL source, String destination) throws OperationFailedException { - PathAddress contentAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, overlayName), - PathElement.pathElement(CONTENT, destination)); - ModelNode op = Util.createOperation(ADD, contentAddress); - - ModelNode contentItem = new ModelNode(); - contentItem.get(URL).set(source.toExternalForm()); - op.get(CONTENT).set(contentItem); - - addRollbackFalse(op); - System.out.println("*** add content operation"); - System.out.println(op.toString()); - - context.addStep(op, getAddHandler(context, contentAddress), OperationContext.Stage.MODEL); - } - - private void addChangeToOperation(OperationContext context, URL source, ManageOverlayHandler.changeToEnum changeTo) { - ModelNode op = Util.createOperation(ManageOverlayHandler.OP, this.pathAddress); - op.get(ManageOverlayHandler.URL.getName()).set(source.toExternalForm()); - op.get(ManageOverlayHandler.CHANGE_TO.getName()).set(changeTo.toString()); - - System.out.println("************change-to operation********************"); - System.out.println(op.toString()); - context.addStep(op, ManageOverlayHandler.INSTANCE, OperationContext.Stage.RUNTIME, false); - }*/ - - private OperationStepHandler getHandler(OperationContext context, PathAddress address, String opName) { + static OperationStepHandler getHandler(OperationContext context, PathAddress address, String opName) { ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); return rootResourceRegistration.getOperationHandler(address, opName); - //return new IgnoreIfResourceExistsHandler(handler); - } - - private void addRollbackFalse(ModelNode modelNode) { - modelNode.get(ROLLBACK_ON_RUNTIME_FAILURE).set(false); } static String getDeploymentName(ModelNode operation) { String deploymentName = Util.getNameFromAddress(operation.get(ADDRESS)); - System.out.println("*** authServerName=" + deploymentName); if (!deploymentName.toLowerCase().endsWith(".war")) { deploymentName += ".war"; } diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java index 11cedce525..24decfcc02 100755 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/AuthServerWriteAttributeHandler.java @@ -50,13 +50,6 @@ public class AuthServerWriteAttributeHandler extends ModelOnlyWriteAttributeHand return; } - System.out.println("**** finishModelStage *****"); - System.out.println("** operation **"); - System.out.println(operation.toString()); - System.out.println("** attributeName=" + attributeName); - System.out.println("** oldValue=" + oldValue); - System.out.println("** newValue=" + newValue); - AuthServerUtil authServerUtil = new AuthServerUtil(operation); boolean isEnabled = isEnabled(model); // is server currently enabled? diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java deleted file mode 100755 index d347071495..0000000000 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/ManageOverlayHandler.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.keycloak.subsystem.extension.authserver; - -import org.jboss.as.controller.AttributeDefinition; -import org.jboss.as.controller.OperationContext; -import org.jboss.as.controller.OperationFailedException; -import org.jboss.as.controller.ServiceVerificationHandler; -import org.jboss.dmr.ModelNode; -import org.jboss.msc.service.ServiceController; - -import java.util.List; -import java.util.Set; -import org.jboss.as.controller.AbstractRuntimeOnlyHandler; -import org.jboss.as.controller.OperationDefinition; -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.SimpleOperationDefinitionBuilder; - -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEPLOYMENT_OVERLAY; -import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; -import org.jboss.as.controller.operations.validation.EnumValidator; -import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; -import org.jboss.as.controller.registry.Resource; -import org.jboss.dmr.ModelType; -import org.keycloak.subsystem.extension.KeycloakAdapterConfigService; - -/** - * Rename the extension of an overlay in the overlays/ directory. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. - */ -public final class ManageOverlayHandler extends AbstractRuntimeOnlyHandler { - - public static final String OP = "change-to"; - - public static ManageOverlayHandler INSTANCE = new ManageOverlayHandler(); - - public enum changeToEnum {deployed, undeployed}; - - protected static final SimpleAttributeDefinition URL = - new SimpleAttributeDefinitionBuilder("url", ModelType.STRING, false) - .setAllowExpression(false) - .build(); - - protected static final SimpleAttributeDefinition CHANGE_TO = - new SimpleAttributeDefinitionBuilder(OP, ModelType.STRING, false) - .setAllowExpression(false) - .setValidator(new EnumValidator(changeToEnum.class, false, false)) - .build(); - - public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder("manage-overlay", AuthServerDefinition.rscDescriptionResolver) - .addParameter(URL) - .addParameter(CHANGE_TO) - .build(); - - private ManageOverlayHandler() { - } - - @Override - protected void executeRuntimeStep(OperationContext context, ModelNode mn) throws OperationFailedException { - System.out.println("Executing!!!!"); - PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement(DEPLOYMENT_OVERLAY, "main-auth-server.war-keycloak-overlay")); - //PathAddress pathAddress = PathAddress.pathAddress(PathElement.pathElement("path", "user.dir")); - ImmutableManagementResourceRegistration rootResourceRegistration = context.getRootResourceRegistration(); - - Resource resource = context.readResourceFromRoot(pathAddress); - Set childAddrs = rootResourceRegistration.getChildAddresses(pathAddress); - Set children = rootResourceRegistration.getChildNames(pathAddress); - - - System.out.println("***************"); - System.out.println("childAddrs=" + childAddrs); - System.out.println("children=" + children); - System.out.println("model=" + resource.getModel()); - System.out.println("children=" + resource.getChildrenNames("deployment")); - context.completeStep(OperationContext.ResultHandler.NOOP_RESULT_HANDLER); - } - -} diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/OverlayKeycloakServerJsonHandler.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/OverlayKeycloakServerJsonHandler.java new file mode 100644 index 0000000000..485c37dd29 --- /dev/null +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/authserver/OverlayKeycloakServerJsonHandler.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.keycloak.subsystem.extension.authserver; + +import org.jboss.as.controller.OperationDefinition; +import org.jboss.as.controller.SimpleOperationDefinitionBuilder; + +/** + * Operation to overlay keycloak-server.json. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class OverlayKeycloakServerJsonHandler extends AbstractAddOverlayHandler { + + public static final String OP = "update-server-config"; + + public static final OverlayKeycloakServerJsonHandler INSTANCE = new OverlayKeycloakServerJsonHandler(); + + public static OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OP, AuthServerDefinition.rscDescriptionResolver) + .addParameter(BYTES_TO_UPLOAD) + .build(); + + @Override + String getOverlayPath(String fileName) { + return "/WEB-INF/classes/META-INF/keycloak-server.json"; + } + +} diff --git a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties index 45c4c6e22c..3239f14040 100755 --- a/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties +++ b/integration/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -9,9 +9,12 @@ keycloak.subsystem.secure-deployment=A deployment secured by Keycloak. keycloak.auth-server=A Keycloak Auth Server keycloak.auth-server.add=Add an Auth Server to the subsystem. keycloak.auth-server.remove=Remove an Auth Server from the subsystem. -keycloak.auth-server.manage-overlay=Internal use only. Do not call from CLI!! -keycloak.auth-server.manage-overlay.url=Internal use only. Do not call from CLI!! The overlay URL that needs its extension to be changed. -keycloak.auth-server.manage-overlay.change-to=Internal use only. Do not call from CLI!! The extension the overlay will be changed to. +keycloak.auth-server.add-provider=Add a provider service jar to the Keycloak auth server. +keycloak.auth-server.add-provider.uploaded-file-name=The file name of the provider service jar to be added or updated. +keycloak.auth-server.add-provider.bytes-to-upload=The bytes of the provider service jar to be added or updated. +keycloak.auth-server.update-server-config=Upload a new keycloak-server.json configuration file for the Keycloak auth server. +keycloak.auth-server.update-server-config.uploaded-file-name=Should be the name keycloak-server.json. +keycloak.auth-server.update-server-config.bytes-to-upload=The bytes of the keycloak-server.json file to be added or updated. keycloak.auth-server.enabled=Enable or disable the Auth Server. keycloak.auth-server.web-context=Web context the auth-server will use. Also, the module name of the auth-server deployment. From e8e50d2d1c08c70ae51bb0189e49beb446fd0697 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 28 Oct 2014 14:03:21 -0400 Subject: [PATCH 4/6] KEYCLOAK-795 Cleanup appliance dist. --- distribution/appliance-dist/assembly.xml | 7 ------- .../appliance-dist/src/main/xslt/standalone.xsl | 11 ----------- 2 files changed, 18 deletions(-) diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml index 0e788d8c34..744440084b 100755 --- a/distribution/appliance-dist/assembly.xml +++ b/distribution/appliance-dist/assembly.xml @@ -39,13 +39,6 @@ org/picketlink/** - - ${project.build.directory}/unpacked/deployments - keycloak/standalone/deployments - - keycloak-ds.xml - - ${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF keycloak/standalone/configuration diff --git a/distribution/appliance-dist/src/main/xslt/standalone.xsl b/distribution/appliance-dist/src/main/xslt/standalone.xsl index 7ed378f148..3cb32a9d2c 100755 --- a/distribution/appliance-dist/src/main/xslt/standalone.xsl +++ b/distribution/appliance-dist/src/main/xslt/standalone.xsl @@ -80,17 +80,6 @@ - - - - - - - - - - - From aa55a86ff6a97b63dbdb0c7751b002c9df1216c4 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Sun, 2 Nov 2014 16:46:41 -0500 Subject: [PATCH 5/6] KEYCLOAK-795 Update documentation. Add latest version of CLI to appliance dist. --- distribution/appliance-dist/assembly.xml | 1 + distribution/modules/build.xml | 4 + distribution/modules/pom.xml | 5 + .../modules/org/jboss/as/cli/main/module.xml | 56 +++++++ .../wildfly-cli-1.0.0.Alpha11-SNAPSHOT.jar | Bin 0 -> 927753 bytes .../en/en-US/modules/server-installation.xml | 140 ++++++++++++++++++ .../en/images/add-provider-dialog.png | Bin 0 -> 92638 bytes .../en/images/add-provider-select.png | Bin 0 -> 75277 bytes docbook/reference/en/images/domain-mode.png | Bin 0 -> 105612 bytes .../en/images/update-server-config-dialog.png | Bin 0 -> 93611 bytes .../en/images/update-server-config-select.png | Bin 0 -> 74708 bytes 11 files changed, 206 insertions(+) create mode 100644 distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/module.xml create mode 100644 distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/wildfly-cli-1.0.0.Alpha11-SNAPSHOT.jar create mode 100644 docbook/reference/en/images/add-provider-dialog.png create mode 100644 docbook/reference/en/images/add-provider-select.png create mode 100644 docbook/reference/en/images/domain-mode.png create mode 100644 docbook/reference/en/images/update-server-config-dialog.png create mode 100644 docbook/reference/en/images/update-server-config-select.png diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml index 744440084b..5f7247b177 100755 --- a/distribution/appliance-dist/assembly.xml +++ b/distribution/appliance-dist/assembly.xml @@ -22,6 +22,7 @@ **/*.sh welcome-content/* **/modules/system/layers/base/org/picketlink/** + **/modules/system/layers/base/org/jboss/as/cli/** diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml index 7495cdc077..f2f0c895d0 100755 --- a/distribution/modules/build.xml +++ b/distribution/modules/build.xml @@ -51,6 +51,10 @@ + + + + diff --git a/distribution/modules/pom.xml b/distribution/modules/pom.xml index 503ea21e6c..cfcfccca7b 100755 --- a/distribution/modules/pom.xml +++ b/distribution/modules/pom.xml @@ -83,6 +83,11 @@ org.bouncycastle bcprov-jdk16 + + org.wildfly.core + wildfly-cli + ${wildfly.core.version} + org.picketlink picketlink-common diff --git a/distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/module.xml b/distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/module.xml new file mode 100644 index 0000000000..c46ff3aa97 --- /dev/null +++ b/distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/module.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/wildfly-cli-1.0.0.Alpha11-SNAPSHOT.jar b/distribution/modules/src/main/resources/modules/org/jboss/as/cli/main/wildfly-cli-1.0.0.Alpha11-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..c105089f0129591fcc34f579f0cbff4c7399721c GIT binary patch literal 927753 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