diff --git a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java index 7ad2d85631..73f221ca8a 100755 --- a/core/src/main/java/org/keycloak/adapters/AdapterConstants.java +++ b/core/src/main/java/org/keycloak/adapters/AdapterConstants.java @@ -9,4 +9,9 @@ public interface AdapterConstants { // URL endpoints public static final String K_LOGOUT = "k_logout"; public static final String K_QUERY_BEARER_TOKEN = "k_query_bearer_token"; + + // This param name is defined again in Keycloak Subsystem class + // org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in + // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. + String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; } diff --git a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java index e4df1240ba..f016b4855f 100755 --- a/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java +++ b/core/src/test/java/org/keycloak/SkeletonKeyTokenTest.java @@ -5,10 +5,11 @@ import org.junit.Test; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.crypto.RSAProvider; -import org.keycloak.util.JsonSerialization; import org.keycloak.representations.SkeletonKeyScope; import org.keycloak.representations.SkeletonKeyToken; +import org.keycloak.util.JsonSerialization; +import java.io.IOException; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -16,61 +17,93 @@ import java.security.KeyPairGenerator; * @author Bill Burke * @version $Revision: 1 $ */ -public class SkeletonKeyTokenTest -{ - @Test - public void testScope() throws Exception - { - SkeletonKeyScope scope2 = new SkeletonKeyScope(); +public class SkeletonKeyTokenTest { + private static class Parser implements Runnable { + private String json; - scope2.add("one", "admin"); - scope2.add("one", "buyer"); - scope2.add("two", "seller"); - String json = JsonSerialization.writeValueAsString(scope2); - System.out.println(json); + private Parser(String json) { + this.json = json; + } + + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + try { + SkeletonKeyScope scope = JsonSerialization.readValue(json.getBytes(), SkeletonKeyScope.class); + } catch (IOException e) { + + } + } + } + } + + @Test + public void testScope() throws Exception { + SkeletonKeyScope scope2 = new SkeletonKeyScope(); + + scope2.add("one", "admin"); + scope2.add("one", "buyer"); + scope2.add("two", "seller"); + String json = JsonSerialization.writeValueAsString(scope2); + System.out.println(json); + + /* + + Thread[] threads = new Thread[1000]; + for (int i = 0; i < 1000; i++) { + threads[i] = new Thread(new Parser(json)); + } + long start = System.currentTimeMillis(); + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + long end = System.currentTimeMillis() - start; + System.out.println("Time took: " + end); + */ - } + } - @Test - public void testToken() throws Exception - { - SkeletonKeyToken token = new SkeletonKeyToken(); - token.id("111"); - token.addAccess("foo").addRole("admin"); - token.addAccess("bar").addRole("user"); + @Test + public void testToken() throws Exception { + SkeletonKeyToken token = new SkeletonKeyToken(); + token.id("111"); + token.addAccess("foo").addRole("admin"); + token.addAccess("bar").addRole("user"); - String json = JsonSerialization.writeValueAsString(token); - System.out.println(json); + String json = JsonSerialization.writeValueAsString(token); + System.out.println(json); - token = JsonSerialization.readValue(json, SkeletonKeyToken.class); - Assert.assertEquals("111", token.getId()); - SkeletonKeyToken.Access foo = token.getResourceAccess("foo"); - Assert.assertNotNull(foo); - Assert.assertTrue(foo.isUserInRole("admin")); + token = JsonSerialization.readValue(json, SkeletonKeyToken.class); + Assert.assertEquals("111", token.getId()); + SkeletonKeyToken.Access foo = token.getResourceAccess("foo"); + Assert.assertNotNull(foo); + Assert.assertTrue(foo.isUserInRole("admin")); - } + } - @Test - public void testRSA() throws Exception - { - SkeletonKeyToken token = new SkeletonKeyToken(); - token.id("111"); - token.addAccess("foo").addRole("admin"); - token.addAccess("bar").addRole("user"); + @Test + public void testRSA() throws Exception { + SkeletonKeyToken token = new SkeletonKeyToken(); + token.id("111"); + token.addAccess("foo").addRole("admin"); + token.addAccess("bar").addRole("user"); - KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - String encoded = new JWSBuilder() - .jsonContent(token) - .rsa256(keyPair.getPrivate()); + String encoded = new JWSBuilder() + .jsonContent(token) + .rsa256(keyPair.getPrivate()); - System.out.println(encoded); + System.out.println(encoded); - JWSInput input = new JWSInput(encoded); + JWSInput input = new JWSInput(encoded); - token = input.readJsonContent(SkeletonKeyToken.class); - Assert.assertEquals("111", token.getId()); - Assert.assertTrue(RSAProvider.verify(input, keyPair.getPublic())); - } + token = input.readJsonContent(SkeletonKeyToken.class); + Assert.assertEquals("111", token.getId()); + Assert.assertTrue(RSAProvider.verify(input, keyPair.getPublic())); + } } diff --git a/distribution/eap6-adapter-zip/assembly.xml b/distribution/eap6-adapter-zip/assembly.xml index 6fe463bec2..d3d334ac02 100755 --- a/distribution/eap6-adapter-zip/assembly.xml +++ b/distribution/eap6-adapter-zip/assembly.xml @@ -11,6 +11,7 @@ ${project.build.directory}/unpacked org/keycloak/keycloak-undertow-adapter/** + org/keycloak/keycloak-wildfly-subsystem/** modules/system/layers/base diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml index 8f3b099d40..16694d4fb2 100755 --- a/distribution/modules/build.xml +++ b/distribution/modules/build.xml @@ -70,6 +70,9 @@ + + + diff --git a/distribution/modules/pom.xml b/distribution/modules/pom.xml index af567d238e..1e29d5209f 100755 --- a/distribution/modules/pom.xml +++ b/distribution/modules/pom.xml @@ -64,6 +64,11 @@ keycloak-wildfly-subsystem ${project.version} + + org.keycloak + keycloak-as7-subsystem + ${project.version} + org.bouncycastle bcprov-jdk16 diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-as7-subsystem/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-as7-subsystem/main/module.xml new file mode 100755 index 0000000000..37603763e7 --- /dev/null +++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-as7-subsystem/main/module.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/wildfly-adapter-zip/assembly.xml b/distribution/wildfly-adapter-zip/assembly.xml index b824efe3f5..76d1f183e4 100755 --- a/distribution/wildfly-adapter-zip/assembly.xml +++ b/distribution/wildfly-adapter-zip/assembly.xml @@ -11,6 +11,7 @@ ${project.build.directory}/unpacked org/keycloak/keycloak-as7-adapter/** + org/keycloak/keycloak-as7-subsystem/** org/bouncycastle/** modules/system/layers/base diff --git a/integration/as7-eap-subsystem/pom.xml b/integration/as7-eap-subsystem/pom.xml new file mode 100755 index 0000000000..db43e456db --- /dev/null +++ b/integration/as7-eap-subsystem/pom.xml @@ -0,0 +1,151 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-parent + 1.0-alpha-2-SNAPSHOT + + + org.keycloak + keycloak-as7-subsystem + 1.0-alpha-2-SNAPSHOT + + Keycloak Wildfly Subsystem + + jar + + + + + maven-compiler-plugin + 2.3.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.8.1 + + false + true + -Xmx512m + + + jboss.home + ${jboss.home} + + + + **/*TestCase.java + + once + + + + + + + + + + org.keycloak + keycloak-as7-adapter + ${project.version} + + + + org.jboss.as + jboss-as-naming + 7.1.1.Final + + + + org.jboss.as + jboss-as-server + 7.1.1.Final + + + + org.jboss.as + jboss-as-ee + 7.1.1.Final + + + + org.jboss.as + jboss-as-web + 7.1.1.Final + + + + org.jboss.logging + jboss-logging + 3.1.0.GA + + + + org.jboss.logging + jboss-logging-processor + + provided + true + 1.0.0.Final + + + + org.jboss.msc + jboss-msc + 1.0.2.GA + + + + junit + junit + test + + + diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java new file mode 100755 index 0000000000..db5d1e5fc2 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java @@ -0,0 +1,57 @@ +package org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AbstractAddStepHandlerWithAttributes extends AbstractAddStepHandler { + protected Collection attributes; + + public AbstractAddStepHandlerWithAttributes(){ //default constructor to preserve backward compatibility + + } + + public AbstractAddStepHandlerWithAttributes(Collection attributes) { + this.attributes = attributes; + } + + /** + * Constructs add handler + * + * @param attributes for which model will be populated + */ + public AbstractAddStepHandlerWithAttributes(AttributeDefinition... attributes) { + if (attributes.length > 0) { + this.attributes = Arrays.asList(attributes); + } else { + this.attributes = Collections.emptySet(); + } + } + + /** + * Populate the given node in the persistent configuration model based on the values in the given operation. + * + * @param operation the operation + * @param model persistent configuration model node that corresponds to the address of {@code operation} + * + * @throws org.jboss.as.controller.OperationFailedException if {@code operation} is invalid or populating the model otherwise fails + */ + protected void populateModel(final ModelNode operation, final ModelNode model) throws OperationFailedException { + if (attributes != null) { + for (AttributeDefinition attr : attributes) { + attr.validateAndSet(operation, model); + } + } + } + + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java new file mode 100755 index 0000000000..9b8a28bd5d --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java @@ -0,0 +1,46 @@ +/* + * 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; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +/** + * Add a credential to a deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class CredentialAddHandler extends AbstractAddStepHandlerWithAttributes { + + public CredentialAddHandler(AttributeDefinition... attributes) { + super(attributes); + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addCredential(operation, model); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java new file mode 100755 index 0000000000..681d4d9caa --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java @@ -0,0 +1,66 @@ +/* + * Copyright 2013 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; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +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.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.registry.OperationEntry; +import org.jboss.dmr.ModelType; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; + +/** + * Defines attributes and operations for a credential. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class CredentialDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "credential"; + + protected static final AttributeDefinition VALUE = + new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false) + .setXmlName("value") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + + public CredentialDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + new CredentialAddHandler(VALUE), + CredentialRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE); + //resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler()); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java new file mode 100755 index 0000000000..6289ff4539 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java @@ -0,0 +1,50 @@ +/* + * 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; + +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Update a credential value. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler { + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder hh) throws OperationFailedException { + + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.updateCredential(operation, attributeName, resolvedValue); + + hh.setHandback(ckService); + + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateCredential(operation, attributeName, valueToRestore); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java new file mode 100755 index 0000000000..1cad10bfb0 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java @@ -0,0 +1,42 @@ +/* + * 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; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a credential from a deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public final class CredentialRemoveHandler extends AbstractRemoveStepHandler { + + public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler(); + + private CredentialRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeCredential(operation); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java new file mode 100755 index 0000000000..c60769eaf4 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -0,0 +1,123 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; +import org.jboss.as.server.deployment.DeploymentPhaseContext; +import org.jboss.as.server.deployment.DeploymentUnit; +import org.jboss.as.server.deployment.DeploymentUnitProcessingException; +import org.jboss.as.server.deployment.DeploymentUnitProcessor; +import org.jboss.as.server.deployment.Phase; +import org.jboss.as.web.deployment.WarMetaData; +import org.jboss.logging.Logger; +import org.jboss.metadata.javaee.spec.ParamValueMetaData; +import org.jboss.metadata.web.jboss.JBossWebMetaData; +import org.jboss.metadata.web.jboss.ValveMetaData; +import org.jboss.metadata.web.spec.LoginConfigMetaData; +import org.keycloak.adapters.as7.KeycloakAuthenticatorValve; + +/** + * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor { + protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class); + + // This param name is defined again in Keycloak Undertow Integration class + // org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in + // 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; + // needs to run before INSTALL_WAR_DEPLOYMENT so that valves are added. + public static final int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT - 1; + + @Override + public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { + DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + String deploymentName = deploymentUnit.getName(); + + KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); + //log.info("********* CHECK KEYCLOAK DEPLOYMENT: " + deploymentName); + if (service.isKeycloakDeployment(deploymentName)) { + + addKeycloakAuthData(phaseContext, deploymentName, service); + } + } + + private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) { + DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); + + addJSONData(service.getJSON(deploymentName), warMetaData); + JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData(); + if (webMetaData == null) { + webMetaData = new JBossWebMetaData(); + warMetaData.setMergedJBossWebMetaData(webMetaData); + } + List valves = webMetaData.getValves(); + if (valves == null) { + valves = new ArrayList(1); + webMetaData.setValves(valves); + } + ValveMetaData valve = new ValveMetaData(); + valve.setValveClass(KeycloakAuthenticatorValve.class.getName()); + valve.setModule("org.keycloak.keycloak-as7-adapter"); + log.info("******* adding Keycloak valve to: " + deploymentName); + valves.add(valve); + + /* + LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); + if (loginConfig == null) { + loginConfig = new LoginConfigMetaData(); + webMetaData.setLoginConfig(loginConfig); + } + loginConfig.setAuthMethod("KEYCLOAK"); + loginConfig.setRealmName(service.getRealmName(deploymentName)); + */ + + } + + private void addJSONData(String json, WarMetaData warMetaData) { + JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData(); + if (webMetaData == null) { + webMetaData = new JBossWebMetaData(); + warMetaData.setMergedJBossWebMetaData(webMetaData); + } + + List contextParams = webMetaData.getContextParams(); + if (contextParams == null) { + contextParams = new ArrayList(); + } + + ParamValueMetaData param = new ParamValueMetaData(); + param.setParamName(AUTH_DATA_PARAM_NAME); + param.setParamValue(json); + contextParams.add(param); + + webMetaData.setContextParams(contextParams); + } + + @Override + public void undeploy(DeploymentUnit du) { + + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java new file mode 100755 index 0000000000..ad31d60a54 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -0,0 +1,214 @@ +/* + * Copyright 2013 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; + +import java.util.HashMap; +import java.util.Map; +import org.jboss.as.controller.OperationContext; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.logging.Logger; +import org.jboss.msc.service.Service; +import org.jboss.msc.service.ServiceController; +import org.jboss.msc.service.ServiceName; +import org.jboss.msc.service.ServiceRegistry; +import org.jboss.msc.service.StartContext; +import org.jboss.msc.service.StartException; +import org.jboss.msc.service.StopContext; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; + +/** + * This service keeps track of the entire Keycloak management model so as to provide + * adapter configuration to each deployment at deploy time. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public final class KeycloakAdapterConfigService implements Service { + protected Logger log = Logger.getLogger(KeycloakAdapterConfigService.class); + private static final String CREDENTIALS_JSON_NAME = "credentials"; + + // Right now this is used as a service, but I'm not sure it really needs to be implemented that way. + // It's also a singleton serving the entire subsystem, but the INSTANCE variable is currently only + // used during initialization of the subsystem. + public static final ServiceName SERVICE_NAME = ServiceName.JBOSS.append("KeycloakAdapterConfigService"); + public static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService(); + + private Map realms = new HashMap(); + private Map deployments = new HashMap(); + + private KeycloakAdapterConfigService() { + + } + + @Override + public void start(StartContext sc) throws StartException { + + } + + @Override + public void stop(StopContext sc) { + + } + + @Override + public KeycloakAdapterConfigService getValue() throws IllegalStateException, IllegalArgumentException { + return this; + } + + public void addRealm(ModelNode operation, ModelNode model) { + this.realms.put(realmNameFromOp(operation), model.clone()); + } + + public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode realm = this.realms.get(realmNameFromOp(operation)); + realm.get(attrName).set(resolvedValue); + } + + public void removeRealm(ModelNode operation) { + this.realms.remove(realmNameFromOp(operation)); + } + + public void addSecureDeployment(ModelNode operation, ModelNode model) { + ModelNode deployment = model.clone(); + deployment.get(RealmDefinition.TAG_NAME).set(realmNameFromOp(operation)); + this.deployments.put(deploymentNameFromOp(operation), deployment); + } + + public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + deployment.get(attrName).set(resolvedValue); + } + + public void removeSecureDeployment(ModelNode operation) { + this.deployments.remove(deploymentNameFromOp(operation)); + } + + public void addCredential(ModelNode operation, ModelNode model) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + credentials = new ModelNode(); + } + + String credentialName = credentialNameFromOp(operation); + credentials.get(credentialName).set(model.get("value").asString()); + + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + deployment.get(CREDENTIALS_JSON_NAME).set(credentials); + } + + public void removeCredential(ModelNode operation) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString()); + } + + String credentialName = credentialNameFromOp(operation); + credentials.remove(credentialName); + } + + public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString()); + } + + String credentialName = credentialNameFromOp(operation); + credentials.get(credentialName).set(resolvedValue); + } + + private ModelNode credentialsFromOp(ModelNode operation) { + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + return deployment.get(CREDENTIALS_JSON_NAME); + } + + private String realmNameFromOp(ModelNode operation) { + return valueFromOpAddress(RealmDefinition.TAG_NAME, operation); + } + + private String deploymentNameFromOp(ModelNode operation) { + return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation); + } + + private String credentialNameFromOp(ModelNode operation) { + return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation); + } + + private String valueFromOpAddress(String addrElement, ModelNode operation) { + String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement); + if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString()); + return deploymentName; + } + + private String getValueOfAddrElement(ModelNode address, String elementName) { + for (ModelNode element : address.asList()) { + if (element.has(elementName)) return element.get(elementName).asString(); + } + + return null; + } + + public String getRealmName(String deploymentName) { + ModelNode deployment = this.deployments.get(deploymentName); + return deployment.get(RealmDefinition.TAG_NAME).asString(); + + } + + public String getJSON(String deploymentName) { + ModelNode deployment = this.deployments.get(deploymentName); + String realmName = deployment.get(RealmDefinition.TAG_NAME).asString(); + ModelNode realm = this.realms.get(realmName); + + ModelNode json = new ModelNode(); + json.get(RealmDefinition.TAG_NAME).set(realmName); + + // Realm values set first. Some can be overridden by deployment values. + setJSONValues(json, realm); + setJSONValues(json, deployment); + return json.toJSONString(true); + } + + private void setJSONValues(ModelNode json, ModelNode values) { + for (Property prop : values.asPropertyList()) { + String name = prop.getName(); + ModelNode value = prop.getValue(); + if (value.isDefined()) { + json.get(name).set(value); + } + } + } + + public boolean isKeycloakDeployment(String deploymentName) { + //log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size()); + + return this.deployments.containsKey(deploymentName); + } + + static KeycloakAdapterConfigService find(ServiceRegistry registry) { + ServiceController container = registry.getService(KeycloakAdapterConfigService.SERVICE_NAME); + if (container != null) { + KeycloakAdapterConfigService service = (KeycloakAdapterConfigService)container.getValue(); + return service; + } + return null; + } + + static KeycloakAdapterConfigService find(OperationContext context) { + return find(context.getServiceRegistry(true)); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java new file mode 100755 index 0000000000..889d8fa936 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 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; + +import org.jboss.as.server.deployment.Attachments; +import org.jboss.as.server.deployment.DeploymentPhaseContext; +import org.jboss.as.server.deployment.DeploymentUnit; +import org.jboss.as.server.deployment.DeploymentUnitProcessingException; +import org.jboss.as.server.deployment.DeploymentUnitProcessor; +import org.jboss.as.server.deployment.module.ModuleDependency; +import org.jboss.as.server.deployment.module.ModuleSpecification; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleIdentifier; +import org.jboss.modules.ModuleLoader; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakDependencyProcessor implements DeploymentUnitProcessor { + + private static final ModuleIdentifier KEYCLOAK_AS7_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-as7-adapter"); + private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core"); + private static final ModuleIdentifier APACHE_HTTPCOMPONENTS = ModuleIdentifier.create("org.apache.httpcomponents"); + + @Override + public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { + final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + + KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); + if (service.isKeycloakDeployment(deploymentUnit.getName())) { + addModules(deploymentUnit); + } + } + + private void addModules(DeploymentUnit deploymentUnit) { + final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION); + final ModuleLoader moduleLoader = Module.getBootModuleLoader(); + + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_AS7_ADAPTER, false, false, true, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE_HTTPCOMPONENTS, false, false, true, false)); + } + + @Override + public void undeploy(DeploymentUnit du) { + + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java new file mode 100755 index 0000000000..e1f59afc84 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013 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; + +import org.jboss.as.controller.Extension; +import org.jboss.as.controller.ExtensionContext; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SubsystemRegistration; +import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; +import org.jboss.as.controller.parsing.ExtensionParsingContext; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.ResourceDefinition; +import org.keycloak.subsystem.logging.KeycloakLogger; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; + + +/** + * Main Extension class for the subsystem. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakExtension implements Extension { + + public static final String SUBSYSTEM_NAME = "keycloak"; + public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.0"; + private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser(); + static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME); + private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions"; + private static final int MANAGEMENT_API_MAJOR_VERSION = 1; + private static final int MANAGEMENT_API_MINOR_VERSION = 0; + private static final int MANAGEMENT_API_MICRO_VERSION = 0; + protected static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME); + private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition(); + static final RealmDefinition REALM_DEFINITION = new RealmDefinition(); + static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition(); + static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition(); + + static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { + StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); + for (String kp : keyPrefix) { + prefix.append('.').append(kp); + } + return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void initializeParsers(final ExtensionParsingContext context) { + context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER); + } + + /** + * {@inheritDoc} + */ + @Override + public void initialize(final ExtensionContext context) { + KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension"); + final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MANAGEMENT_API_MAJOR_VERSION, + MANAGEMENT_API_MINOR_VERSION); + + ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE); + ManagementResourceRegistration realmRegistration = registration.registerSubModel(REALM_DEFINITION); + ManagementResourceRegistration secureDeploymentRegistration = realmRegistration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION); + secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); + + subsystem.registerXMLElementWriter(PARSER); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java new file mode 100755 index 0000000000..7bb610b5f1 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013 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; + +import java.util.List; + +import org.jboss.as.controller.AbstractBoottimeAddStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.as.server.AbstractDeploymentChainStep; +import org.jboss.as.server.DeploymentProcessorTarget; +import org.jboss.as.server.deployment.Phase; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +/** + * The Keycloak subsystem add update handler. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler { + + static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd(); + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + model.setEmptyObject(); + } + + @Override + protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) { + context.addStep(new AbstractDeploymentChainStep() { + @Override + protected void execute(DeploymentProcessorTarget processorTarget) { + processorTarget.addDeploymentProcessor(Phase.DEPENDENCIES, 0, new KeycloakDependencyProcessor()); + + + processorTarget.addDeploymentProcessor(KeycloakAdapterConfigDeploymentProcessor.PHASE, + KeycloakAdapterConfigDeploymentProcessor.PRIORITY, + new KeycloakAdapterConfigDeploymentProcessor()); + } + }, OperationContext.Stage.RUNTIME); + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + super.performRuntime(context, operation, model, verificationHandler, newControllers); + + ServiceController controller = context.getServiceTarget() + .addService(KeycloakAdapterConfigService.SERVICE_NAME, KeycloakAdapterConfigService.INSTANCE) + .addListener(verificationHandler) + .setInitialMode(ServiceController.Mode.ACTIVE) + .install(); + newControllers.add(controller); + } + + @Override + protected boolean requiresRuntimeVerification() { + return false; + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java new file mode 100755 index 0000000000..d3b3f69af7 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java @@ -0,0 +1,51 @@ +/* + * 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; + +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.ReloadRequiredRemoveStepHandler; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.registry.OperationEntry; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; + +/** + * Definition of subsystem=keycloak. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakSubsystemDefinition extends SimpleResourceDefinition { + protected KeycloakSubsystemDefinition() { + super(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + KeycloakExtension.getResourceDescriptionResolver("subsystem"), + KeycloakSubsystemAdd.INSTANCE, + ReloadRequiredRemoveStepHandler.INSTANCE + ); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE); + //resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java new file mode 100755 index 0000000000..0cb196bc6e --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java @@ -0,0 +1,226 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.persistence.SubsystemMarshallingContext; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.staxmapper.XMLElementReader; +import org.jboss.staxmapper.XMLElementWriter; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * The subsystem parser, which uses stax to read and write to and from xml + */ +class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader>, XMLElementWriter { + + /** + * {@inheritDoc} + */ + @Override + public void readElement(final XMLExtendedStreamReader reader, final List list) throws XMLStreamException { + // Require no attributes + ParseUtils.requireNoAttributes(reader); + ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM)); + list.add(addKeycloakSub); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + if (!reader.getLocalName().equals("realm")) { + throw ParseUtils.unexpectedElement(reader); + } + readRealm(reader, list); + } + } + + // used for debugging + private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException { + return reader.nextTag(); + } + + private void readRealm(XMLExtendedStreamReader reader, List list) throws XMLStreamException { + String realmName = readNameAttribute(reader); + ModelNode composite = new ModelNode(); + composite.get(ModelDescriptionConstants.OP_ADDR).setEmptyList(); + composite.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.COMPOSITE); + ModelNode addRealm = new ModelNode(); + addRealm.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(RealmDefinition.TAG_NAME, realmName)); + addRealm.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + + List resourcesToAdd = new ArrayList(); + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + if (tagName.equals(SecureDeploymentDefinition.TAG_NAME)) { + readDeployment(reader, addr, resourcesToAdd); + continue; + } + + SimpleAttributeDefinition def = RealmDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addRealm, reader); + } + + if (!RealmDefinition.validateTruststoreSetIfRequired(addRealm)) { + //TODO: externalize the message + throw new XMLStreamException("truststore and truststore-password must be set if both ssl-not-required and disable-trust-maanger are false."); + } + + ModelNode steps = new ModelNode(); + steps.add(addRealm); + for (ModelNode resource : resourcesToAdd) { + steps.add(resource); + } + composite.get(ModelDescriptionConstants.STEPS).set(steps); + + list.add(composite); + } + + private void readDeployment(XMLExtendedStreamReader reader, PathAddress parent, List resourcesToAdd) throws XMLStreamException { + String name = readNameAttribute(reader); + ModelNode addSecureDeployment = new ModelNode(); + addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name)); + addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + List credentialsToAdd = new ArrayList(); + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + if (tagName.equals(CredentialDefinition.TAG_NAME)) { + readCredential(reader, addr, credentialsToAdd); + continue; + } + + SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader); + } + // Must add credentials after the deployment is added. + resourcesToAdd.add(addSecureDeployment); + resourcesToAdd.addAll(credentialsToAdd); + } + + public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List credentialsToAdd) throws XMLStreamException { + String name = readNameAttribute(reader); + ModelNode addCredential = new ModelNode(); + addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name)); + addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText()); + credentialsToAdd.add(addCredential); + } + + // expects that the current tag will have one single attribute called "name" + private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException { + String name = null; + for (int i = 0; i < reader.getAttributeCount(); i++) { + String attr = reader.getAttributeLocalName(i); + if (attr.equals("name")) { + name = reader.getAttributeValue(i); + continue; + } + throw ParseUtils.unexpectedAttribute(reader, i); + } + if (name == null) { + throw ParseUtils.missingRequired(reader, Collections.singleton("name")); + } + return name; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException { + context.startSubsystemElement(KeycloakExtension.NAMESPACE, false); + writeRealms(writer, context); + writer.writeEndElement(); + } + + private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) { + return; + } + for (Property realm : context.getModelNode().get(RealmDefinition.TAG_NAME).asPropertyList()) { + writer.writeStartElement(RealmDefinition.TAG_NAME); + writer.writeAttribute("name", realm.getName()); + ModelNode realmElements = realm.getValue(); + for (AttributeDefinition element : RealmDefinition.ALL_ATTRIBUTES) { + element.marshallAsElement(realmElements, writer); + } + + ModelNode deployments = realmElements.get(SecureDeploymentDefinition.TAG_NAME); + if (deployments.isDefined()) { + writeSecureDeployments(writer, deployments); + } + + writer.writeEndElement(); + } + } + + private void writeSecureDeployments(XMLExtendedStreamWriter writer, ModelNode deployments) throws XMLStreamException { + for (Property deployment : deployments.asPropertyList()) { + writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME); + writer.writeAttribute("name", deployment.getName()); + ModelNode deploymentElements = deployment.getValue(); + for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) { + element.marshallAsElement(deploymentElements, writer); + } + + ModelNode credentials = deploymentElements.get(CredentialDefinition.TAG_NAME); + if (credentials.isDefined()) { + writeCredentials(writer, credentials); + } + + writer.writeEndElement(); + } + } + + private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException { + for (Property credential : credentials.asPropertyList()) { + writer.writeStartElement(CredentialDefinition.TAG_NAME); + writer.writeAttribute("name", credential.getName()); + String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString(); + writeCharacters(writer, credentialValue); + writer.writeEndElement(); + } + } + + // code taken from org.jboss.as.controller.AttributeMarshaller + private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException { + if (content.indexOf('\n') > -1) { + // Multiline content. Use the overloaded variant that staxmapper will format + writer.writeCharacters(content); + } else { + // Staxmapper will just output the chars without adding newlines if this is used + char[] chars = content.toCharArray(); + writer.writeCharacters(chars, 0, chars.length); + } + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java new file mode 100755 index 0000000000..ea080aed3a --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 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; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.msc.service.ServiceController; + +/** + * Add a new realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public final class RealmAddHandler extends AbstractAddStepHandler { + + public static RealmAddHandler INSTANCE = new RealmAddHandler(); + + private RealmAddHandler() {} + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + // TODO: localize exception. get id number + if (!operation.get(OP).asString().equals(ADD)) { + throw new OperationFailedException("Unexpected operation for add realm. operation=" + operation.toString()); + } + + for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) { + attrib.validateAndSet(operation, model); + } + + if (!RealmDefinition.validateTruststoreSetIfRequired(model.clone())) { + //TODO: externalize message + throw new OperationFailedException("truststore and truststore-password must be set if both ssl-not-required and disable-trust-maanger are false."); + } + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addRealm(operation, model); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java new file mode 100755 index 0000000000..295951d443 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java @@ -0,0 +1,177 @@ +/* + * Copyright 2013 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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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.operations.validation.IntRangeValidator; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.registry.OperationEntry; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; + +/** + * Defines attributes and operations for the Realm + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "realm"; + + protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY = + new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, false) + .setXmlName("realm-public-key") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + protected static final SimpleAttributeDefinition AUTH_SERVER_URL = + new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, false) + .setXmlName("auth-server-url") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + protected static final SimpleAttributeDefinition SSL_NOT_REQUIRED = + new SimpleAttributeDefinitionBuilder("ssl-not-required", ModelType.BOOLEAN, true) + .setXmlName("ssl-not-required") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME = + new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true) + .setXmlName("allow-any-hostname") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER = + new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true) + .setXmlName("disable-trust-manager") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition TRUSTSTORE = + new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true) + .setXmlName("truststore") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD = + new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true) + .setXmlName("truststore-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE = + new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true) + .setXmlName("connection-pool-size") + .setAllowExpression(true) + .setValidator(new IntRangeValidator(0, true)) + .build(); + + protected static final List REALM_ONLY_ATTRIBUTES = new ArrayList(); + static { + REALM_ONLY_ATTRIBUTES.add(REALM_PUBLIC_KEY); + REALM_ONLY_ATTRIBUTES.add(AUTH_SERVER_URL); + REALM_ONLY_ATTRIBUTES.add(TRUSTSTORE); + REALM_ONLY_ATTRIBUTES.add(TRUSTSTORE_PASSWORD); + REALM_ONLY_ATTRIBUTES.add(SSL_NOT_REQUIRED); + REALM_ONLY_ATTRIBUTES.add(ALLOW_ANY_HOSTNAME); + REALM_ONLY_ATTRIBUTES.add(DISABLE_TRUST_MANAGER); + REALM_ONLY_ATTRIBUTES.add(CONNECTION_POOL_SIZE); + } + + protected static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0])); + + public RealmDefinition() { + super(PathElement.pathElement("realm"), + KeycloakExtension.getResourceDescriptionResolver("realm"), + RealmAddHandler.INSTANCE, + RealmRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE); + //resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + + for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { + //TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired + resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler); + } + } + + /** + * truststore and truststore-password must be set if ssl-not-required and disable-trust-manager are both false. + * + * @param attributes The full set of attributes. + * + * @return true if the attributes are valid, false otherwise. + */ + public static boolean validateTruststoreSetIfRequired(ModelNode attributes) { + if (!isSet(attributes, SSL_NOT_REQUIRED) && !isSet(attributes, DISABLE_TRUST_MANAGER)) { + if (!(isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD))) { + return false; + } + } + + return true; + } + + private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) { + ModelNode attribute = attributes.get(def.getName()); + + if (def.getType() == ModelType.BOOLEAN) { + return attribute.isDefined() && attribute.asBoolean(); + } + + return attribute.isDefined() && !attribute.asString().isEmpty(); + } + + public static SimpleAttributeDefinition lookup(String name) { + return DEFINITION_LOOKUP.get(name); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java new file mode 100755 index 0000000000..a5c5441554 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 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; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public final class RealmRemoveHandler extends AbstractRemoveStepHandler { + + public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler(); + + private RealmRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeRealm(operation); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java new file mode 100755 index 0000000000..f6847b5d5d --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 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; + +import java.util.List; +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Update an attribute on a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler { + + public RealmWriteAttributeHandler(List definitions) { + this(definitions.toArray(new AttributeDefinition[definitions.size()])); + } + + public RealmWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, HandbackHolder hh) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.updateRealm(operation, attributeName, resolvedValue); + + hh.setHandback(ckService); + + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateRealm(operation, attributeName, valueToRestore); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java new file mode 100755 index 0000000000..c5f795c4e1 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 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; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.msc.service.ServiceController; + +/** + * Add a deployment to a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public final class SecureDeploymentAddHandler extends AbstractAddStepHandler { + + public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler(); + + private SecureDeploymentAddHandler() {} + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + // TODO: localize exception. get id number + if (!operation.get(OP).asString().equals(ADD)) { + throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString()); + } + + for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) { + attr.validateAndSet(operation, model); + } + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addSecureDeployment(operation, model); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java new file mode 100755 index 0000000000..91fab8d429 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java @@ -0,0 +1,112 @@ +/* + * Copyright 2013 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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +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.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.registry.OperationEntry; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; + +/** + * Defines attributes and operations for a secure-deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "secure-deployment"; + + protected static final SimpleAttributeDefinition RESOURCE = + new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true) + .setXmlName("resource") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS = + new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true) + .setXmlName("use-resource-role-mappings") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition BEARER_ONLY = + new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true) + .setXmlName("bearer-only") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + + protected static final List DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); + static { + DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE); + DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS); + DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY); + } + + protected static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES); + + public SecureDeploymentDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + SecureDeploymentAddHandler.INSTANCE, + SecureDeploymentRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE); + //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); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java new file mode 100755 index 0000000000..da0ca7e19d --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 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; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a secure-deployment from a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler { + + public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler(); + + private SecureDeploymentRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeSecureDeployment(operation); + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java new file mode 100755 index 0000000000..8cc1f604d8 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 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; + +import java.util.List; +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.dmr.ModelNode; + +/** + * Update an attribute on a secure-deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler { + + public SecureDeploymentWriteAttributeHandler(List definitions) { + this(definitions.toArray(new AttributeDefinition[definitions.size()])); + } + + public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, HandbackHolder hh) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + hh.setHandback(ckService); + ckService.updateSecureDeployment(operation, attributeName, resolvedValue); + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateSecureDeployment(operation, attributeName, valueToRestore); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java new file mode 100755 index 0000000000..e95bb5da4f --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 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; + +import java.util.ArrayList; +import java.util.List; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.operations.validation.IntRangeValidator; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * Defines attributes that can be present in both a realm and an application (secure-deployment). + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SharedAttributeDefinitons { + + protected static final SimpleAttributeDefinition ENABLE_CORS = + new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true) + .setXmlName("enable-cors") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEYSTORE = + new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true) + .setXmlName("client-keystore") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD = + new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true) + .setXmlName("client-keystore-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD = + new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true) + .setXmlName("client-key-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CORS_MAX_AGE = + new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true) + .setXmlName("cors-max-age") + .setAllowExpression(true) + .setValidator(new IntRangeValidator(-1, true)) + .build(); + protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS = + new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true) + .setXmlName("cors-allowed-headers") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS = + new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true) + .setXmlName("cors-allowed-methods") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition EXPOSE_TOKEN = + new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true) + .setXmlName("expose-token") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + + + protected static final List ATTRIBUTES = new ArrayList(); + static { + ATTRIBUTES.add(ENABLE_CORS); + ATTRIBUTES.add(CLIENT_KEYSTORE); + ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD); + ATTRIBUTES.add(CLIENT_KEY_PASSWORD); + ATTRIBUTES.add(CORS_MAX_AGE); + ATTRIBUTES.add(CORS_ALLOWED_HEADERS); + ATTRIBUTES.add(CORS_ALLOWED_METHODS); + ATTRIBUTES.add(EXPOSE_TOKEN); + } + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java new file mode 100755 index 0000000000..9d376e274d --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java @@ -0,0 +1,42 @@ +package org.keycloak.subsystem.extension; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class Util { + public static ModelNode createAddOperation(final PathAddress address) { + return createOperation(ModelDescriptionConstants.ADD, address); + } + + public static ModelNode createAddOperation() { + return createEmptyOperation(ModelDescriptionConstants.ADD, null); + } + + public static ModelNode createRemoveOperation(final PathAddress address) { + return createOperation(ModelDescriptionConstants.REMOVE, address); + } + + public static ModelNode createOperation(final String operationName, final PathAddress address) { + return createEmptyOperation(operationName, address); + } + + public static ModelNode createEmptyOperation(String operationName, final PathAddress address) { + ModelNode op = new ModelNode(); + op.get(OP).set(operationName); + if (address != null) { + op.get(OP_ADDR).set(address.toModelNode()); + } else { + // Just establish the standard structure; caller can fill in address later + op.get(OP_ADDR); + } + return op; + } +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java new file mode 100755 index 0000000000..fb45f69913 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013 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.logging; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; +import org.jboss.logging.MessageLogger; + +/** + * This interface to be fleshed out later when error messages are fully externalized. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +@MessageLogger(projectCode = "KEYCLOAK") +public interface KeycloakLogger extends BasicLogger { + + /** + * A logger with a category of the package name. + */ + KeycloakLogger ROOT_LOGGER = Logger.getMessageLogger(KeycloakLogger.class, "org.jboss.keycloak"); + +} diff --git a/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java new file mode 100755 index 0000000000..06a6678b26 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013 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.logging; + +import org.jboss.logging.MessageBundle; +import org.jboss.logging.Messages; + +/** + * This interface to be fleshed out later when error messages are fully externalized. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc. + */ +@MessageBundle(projectCode = "TLIP") +public interface KeycloakMessages { + + /** + * The messages + */ + KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class); +} diff --git a/integration/as7-eap-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/integration/as7-eap-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension new file mode 100755 index 0000000000..6a7d631da0 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension @@ -0,0 +1 @@ +org.keycloak.subsystem.extension.KeycloakExtension diff --git a/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties new file mode 100755 index 0000000000..5234a327e3 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -0,0 +1,49 @@ +keycloak.subsystem=Keycloak subsystem +keycloak.subsystem.add=Operation Adds Keycloak subsystem +keycloak.subsystem.remove=Operation removes Keycloak subsystem +keycloak.subsystem.realm=A Keycloak realm. + +keycloak.realm=A Keycloak realm. +keycloak.realm.add=Add a realm definition to the subsystem. +keycloak.realm.remove=Remove a realm from the subsystem. +keycloak.realm.realm-public-key=TODO: fill in help text +keycloak.realm.auth-server-url=TODO: fill in help text +keycloak.realm.disable-trust-manager=TODO: fill in help text +keycloak.realm.ssl-not-required=TODO: fill in help text +keycloak.realm.allow-any-hostname=TODO: fill in help text +keycloak.realm.truststore=TODO: fill in help text +keycloak.realm.truststore-password=TODO: fill in help text +keycloak.realm.connection-pool-size=TODO: fill in help text +keycloak.realm.enable-cors=TODO: fill in help text +keycloak.realm.client-keystore=TODO: fill in help text +keycloak.realm.client-keystore-password=TODO: fill in help text +keycloak.realm.client-key-password=TODO: fill in help text +keycloak.realm.cors-max-age=TODO: fill in help text +keycloak.realm.cors-allowed-headers=TODO: fill in help text +keycloak.realm.cors-allowed-methods=TODO: fill in help text +keycloak.realm.expose-token=TODO: fill in help text + +keycloak.realm.secure-deployment=A deployment secured by Keycloak + +keycloak.secure-deployment=A deployment secured by Keycloak +keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak +keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak +keycloak.secure-deployment.resource=TODO: fill in help text +keycloak.secure-deployment.use-resource-role-mappings=TODO: fill in help text +keycloak.secure-deployment.credentials=TODO: fill in help text +keycloak.secure-deployment.bearer-only=TODO: fill in help text +keycloak.secure-deployment.enable-cors=TODO: fill in help text +keycloak.secure-deployment.client-keystore=TODO: fill in help text +keycloak.secure-deployment.client-keystore-password=TODO: fill in help text +keycloak.secure-deployment.client-key-password=TODO: fill in help text +keycloak.secure-deployment.cors-max-age=TODO: fill in help text +keycloak.secure-deployment.cors-allowed-headers=TODO: fill in help text +keycloak.secure-deployment.cors-allowed-methods=TODO: fill in help text +keycloak.secure-deployment.expose-token=TODO: fill in help text + +keycloak.secure-deployment.credential=TODO: fill in help text + +keycloak.credential=TODO: fill in help text +keycloak.credential.value=TODO: fill in help text +keycloak.credential.add=TODO: fill in help text +keycloak.credential.remove=TODO: fill in help text \ No newline at end of file diff --git a/integration/as7-eap-subsystem/src/main/resources/schema/keycloak_1_0.xsd b/integration/as7-eap-subsystem/src/main/resources/schema/keycloak_1_0.xsd new file mode 100755 index 0000000000..9cdbbba980 --- /dev/null +++ b/integration/as7-eap-subsystem/src/main/resources/schema/keycloak_1_0.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + The name of the realm. + + + + + + + + + + + + + + + + + + + + + + + + The name of the deployment. + + + + + + + + + + + + + + + + The name of the credential. + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/as7-eap-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java b/integration/as7-eap-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java new file mode 100755 index 0000000000..5cff75e2c5 --- /dev/null +++ b/integration/as7-eap-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 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; + + +import org.jboss.dmr.ModelNode; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmDefinitionTestCase { + + private ModelNode model; + + @Before + public void setUp() { + model = new ModelNode(); + model.get("realm").set("demo"); + model.get("resource").set("customer-portal"); + model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"); + model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/login"); + model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes"); + model.get("expose-token").set(true); + ModelNode credential = new ModelNode(); + credential.get("password").set("password"); + model.get("credentials").set(credential); + } + + @Test + public void testIsTruststoreSetIfRequired() throws Exception { + model.get("ssl-not-required").set(true); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(true); + model.get("disable-trust-manager").set(false); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + Assert.assertFalse(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + Assert.assertFalse(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + model.get("truststore-password").set("password"); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + } + +} diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaAdapterConfigLoader.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaAdapterConfigLoader.java index 604a2ed741..f1abb9ca54 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaAdapterConfigLoader.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/config/CatalinaAdapterConfigLoader.java @@ -1,26 +1,46 @@ package org.keycloak.adapters.as7.config; import org.apache.catalina.Context; +import org.jboss.logging.Logger; +import org.keycloak.adapters.AdapterConstants; import org.keycloak.adapters.config.RealmConfigurationLoader; +import javax.servlet.ServletContext; +import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; public class CatalinaAdapterConfigLoader extends RealmConfigurationLoader { + private static final Logger log = Logger.getLogger(CatalinaAdapterConfigLoader.class); + + private InputStream getJSONFromServletContext(ServletContext servletContext) { + String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); + if (json == null) { + return null; + } + log.info("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME); + log.info(json); + return new ByteArrayInputStream(json.getBytes()); + } public CatalinaAdapterConfigLoader(Context context) { - InputStream is = null; - String path = context.getServletContext().getInitParameter("keycloak.config.file"); - if (path == null) { - is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"); - } else { - try { - is = new FileInputStream(path); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); + log.info("******* Loading adapter config."); + InputStream is = getJSONFromServletContext(context.getServletContext()); + if (is == null) { + String path = context.getServletContext().getInitParameter("keycloak.config.file"); + if (path == null) { + log.info("**** using /WEB-INF/keycloak.json"); + is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } } } + if (is == null) throw new RuntimeException("Could not find keycloak config."); loadConfig(is); } diff --git a/integration/pom.xml b/integration/pom.xml index b0e25d9fdc..8a81e9db53 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -21,6 +21,7 @@ as7-eap6/adapter undertow wildfly-subsystem + as7-eap-subsystem js diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java index 6be2346f59..04e0d64130 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java @@ -13,6 +13,7 @@ import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletSessionConfig; import java.io.ByteArrayInputStream; import org.jboss.logging.Logger; +import org.keycloak.adapters.AdapterConstants; import org.keycloak.adapters.config.RealmConfiguration; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.adapters.config.RealmConfigurationLoader; @@ -26,10 +27,6 @@ import java.util.Map; * @version $Revision: 1 $ */ public class KeycloakServletExtension implements ServletExtension { - // This param name is defined again in Keycloak Subsystem class - // org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in - // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. - public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; protected Logger log = Logger.getLogger(KeycloakServletExtension.class); @@ -47,7 +44,7 @@ public class KeycloakServletExtension implements ServletExtension { } private InputStream getJSONFromServletContext(ServletContext servletContext) { - String json = servletContext.getInitParameter(AUTH_DATA_PARAM_NAME); + String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME); if (json == null) { return null; } @@ -61,9 +58,9 @@ public class KeycloakServletExtension implements ServletExtension { return; } log.info("KeycloakServletException initialization"); - InputStream is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json"); + InputStream is = getJSONFromServletContext(servletContext); if (is == null) { - is = getJSONFromServletContext(servletContext); + is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json"); } if (is == null) throw new RuntimeException("Unable to find realm config in /WEB-INF/keycloak.json or in keycloak subsystem."); RealmConfigurationLoader loader = new RealmConfigurationLoader(is); 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 40b3ec8fb3..9599158266 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 @@ -45,7 +45,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP 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 int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT - 1; @Override public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java old mode 100644 new mode 100755 index 7ecdcb3fc5..d2fdbd2145 --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java @@ -87,7 +87,7 @@ public class RealmDefinition extends SimpleResourceDefinition { new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true) .setXmlName("connection-pool-size") .setAllowExpression(true) - .setValidator(new IntRangeValidator(0)) + .setValidator(new IntRangeValidator(0, true)) .build(); protected static final List REALM_ONLY_ATTRIBUTES = new ArrayList(); diff --git a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java old mode 100644 new mode 100755 index db0ccf8986..e95bb5da4f --- a/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java +++ b/integration/wildfly-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java @@ -60,7 +60,7 @@ public class SharedAttributeDefinitons { new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true) .setXmlName("cors-max-age") .setAllowExpression(true) - .setValidator(new IntRangeValidator(-1)) + .setValidator(new IntRangeValidator(-1, true)) .build(); protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS = new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true)