From f3adf02eead7f2b9b025d4178a2910e2bd683b1b Mon Sep 17 00:00:00 2001 From: apenders Date: Tue, 2 Dec 2014 15:33:15 -0700 Subject: [PATCH 1/3] Added CORS preflight support for realms//tokens/refresh --- .../protocol/oidc/OpenIDConnectService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java index 2c47adbc8b..44b876c8c0 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OpenIDConnectService.java @@ -441,6 +441,21 @@ public class OpenIDConnectService { return Response.ok(token, MediaType.APPLICATION_JSON_TYPE).build(); } + /** + * CORS preflight path for refresh token requests + * + * @return + */ + @Path("refresh") + @OPTIONS + @Produces(MediaType.APPLICATION_JSON) + public Response refreshAccessTokenPreflight() { + if (logger.isDebugEnabled()) { + logger.debugv("cors request from: {0}", request.getHttpHeaders().getRequestHeaders().getFirst("Origin")); + } + return Cors.add(request, Response.ok()).auth().preflight().build(); + } + /** * URL for making refresh token requests. * From a3002cfee6ccf6b35e257faa2fb43304c1ae2150 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 2 Dec 2014 16:09:22 -0500 Subject: [PATCH 2/3] Remove keycloak subsystem from AS7 dist. --- distribution/as7-adapter-zip/assembly.xml | 2 ++ distribution/eap6-adapter-zip/assembly.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/distribution/as7-adapter-zip/assembly.xml b/distribution/as7-adapter-zip/assembly.xml index 171873c444..958fc88b3a 100755 --- a/distribution/as7-adapter-zip/assembly.xml +++ b/distribution/as7-adapter-zip/assembly.xml @@ -13,6 +13,8 @@ org/picketlink/** org/keycloak/keycloak-undertow-adapter/** org/keycloak/keycloak-wildfly-adapter/** + org/jboss/** + org/keycloak/keycloak-subsystem/** modules diff --git a/distribution/eap6-adapter-zip/assembly.xml b/distribution/eap6-adapter-zip/assembly.xml index 99468a6e17..10afff773e 100755 --- a/distribution/eap6-adapter-zip/assembly.xml +++ b/distribution/eap6-adapter-zip/assembly.xml @@ -13,6 +13,7 @@ org/picketlink/** org/keycloak/keycloak-undertow-adapter/** org/keycloak/keycloak-wildfly-adapter/** + org/jboss/** modules/system/layers/base From 952436f129b4cfab580061cc56e7eea3fb20aeab Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Thu, 4 Dec 2014 14:53:14 -0500 Subject: [PATCH 3/3] Restore old AS7 subystem. --- distribution/eap6-adapter-zip/assembly.xml | 1 + distribution/modules/build.xml | 3 + distribution/modules/pom.xml | 5 + distribution/wildfly-adapter-zip/assembly.xml | 1 + .../modules/MigrationFromOlderVersions.xml | 4 +- .../en/en-US/modules/jboss-adapter.xml | 1 + .../en/en-US/modules/server-installation.xml | 50 +--- examples/demo-template/README.md | 14 ++ integration/keycloak-as7-subsystem/pom.xml | 149 ++++++++++++ .../AbstractAddStepHandlerWithAttributes.java | 57 +++++ .../extension/CredentialAddHandler.java | 46 ++++ .../extension/CredentialDefinition.java | 66 +++++ .../CredentialReadWriteAttributeHandler.java | 50 ++++ .../extension/CredentialRemoveHandler.java | 42 ++++ ...cloakAdapterConfigDeploymentProcessor.java | 139 +++++++++++ .../KeycloakAdapterConfigService.java | 214 +++++++++++++++++ .../KeycloakDependencyProcessor.java | 66 +++++ .../extension/KeycloakExtension.java | 85 +++++++ .../extension/KeycloakSubsystemAdd.java | 76 ++++++ .../KeycloakSubsystemDefinition.java | 51 ++++ .../extension/KeycloakSubsystemParser.java | 223 +++++++++++++++++ .../subsystem/extension/RealmAddHandler.java | 66 +++++ .../subsystem/extension/RealmDefinition.java | 91 +++++++ .../extension/RealmRemoveHandler.java | 41 ++++ .../extension/RealmWriteAttributeHandler.java | 60 +++++ .../extension/SecureDeploymentAddHandler.java | 61 +++++ .../extension/SecureDeploymentDefinition.java | 127 ++++++++++ .../SecureDeploymentRemoveHandler.java | 41 ++++ ...SecureDeploymentWriteAttributeHandler.java | 59 +++++ .../extension/SharedAttributeDefinitons.java | 227 ++++++++++++++++++ .../keycloak/subsystem/extension/Util.java | 42 ++++ .../subsystem/logging/KeycloakLogger.java | 49 ++++ .../subsystem/logging/KeycloakMessages.java | 34 +++ .../org.jboss.as.controller.Extension | 1 + .../extension/LocalDescriptions.properties | 70 ++++++ .../main/resources/schema/keycloak_1_0.xsd | 94 ++++++++ .../extension/RealmDefinitionTestCase.java | 88 +++++++ integration/pom.xml | 1 + 38 files changed, 2449 insertions(+), 46 deletions(-) create mode 100755 integration/keycloak-as7-subsystem/pom.xml create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java create mode 100755 integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java create mode 100755 integration/keycloak-as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension create mode 100755 integration/keycloak-as7-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties create mode 100755 integration/keycloak-as7-subsystem/src/main/resources/schema/keycloak_1_0.xsd create mode 100755 integration/keycloak-as7-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java diff --git a/distribution/eap6-adapter-zip/assembly.xml b/distribution/eap6-adapter-zip/assembly.xml index 10afff773e..b3ddba6f0a 100755 --- a/distribution/eap6-adapter-zip/assembly.xml +++ b/distribution/eap6-adapter-zip/assembly.xml @@ -13,6 +13,7 @@ org/picketlink/** org/keycloak/keycloak-undertow-adapter/** org/keycloak/keycloak-wildfly-adapter/** + org/keycloak/keycloak-as7-subsystem/** org/jboss/** modules/system/layers/base diff --git a/distribution/modules/build.xml b/distribution/modules/build.xml index a9da469d1a..14ed2cd3d6 100755 --- a/distribution/modules/build.xml +++ b/distribution/modules/build.xml @@ -86,6 +86,9 @@ + + + diff --git a/distribution/modules/pom.xml b/distribution/modules/pom.xml index f3a2a088c9..5a286abf34 100755 --- a/distribution/modules/pom.xml +++ b/distribution/modules/pom.xml @@ -52,6 +52,11 @@ keycloak-subsystem ${project.version} + + org.keycloak + keycloak-as7-subsystem + ${project.version} + org.bouncycastle bcprov-jdk16 diff --git a/distribution/wildfly-adapter-zip/assembly.xml b/distribution/wildfly-adapter-zip/assembly.xml index ea0599361b..82a8fb98f3 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/** org/picketlink/** diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml index 2e40ef4c7f..df3e3715a5 100755 --- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -87,7 +87,9 @@ JavaScript adapter now has idToken and idTokenParsed properties. If you use idToken to retrieve first name, email, etc. you need to change this to idTokenParsed. The as7-eap-subsystem and keycloak-wildfly-subsystem have been merged into one keycloak-subsystem. If you have an existing standalone.xml - or domain.xml, you will need edit near the top of the file and change the extension module name to org.keycloak.keycloak-subsystem + or domain.xml, you will need edit near the top of the file and change the extension module name to org.keycloak.keycloak-subsystem. + For AS7 only, the extension module name is org.keycloak.keycloak-as7-subsystem. + Server installation is no longer supported on AS7. You can still use AS7 as an application client.
diff --git a/docbook/reference/en/en-US/modules/jboss-adapter.xml b/docbook/reference/en/en-US/modules/jboss-adapter.xml index a172ab09ca..73d194e2b4 100755 --- a/docbook/reference/en/en-US/modules/jboss-adapter.xml +++ b/docbook/reference/en/en-US/modules/jboss-adapter.xml @@ -56,6 +56,7 @@ $ unzip keycloak-as7-adapter-dist.zip ]]> +For AS7, the extension module is org.keycloak.keycloak-as7-sybsystem. Finally, you must specify a shared keycloak security domain. diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml index d81d16c70b..6fe16368e7 100755 --- a/docbook/reference/en/en-US/modules/server-installation.xml +++ b/docbook/reference/en/en-US/modules/server-installation.xml @@ -392,47 +392,8 @@ keycloak-war-dist-all-&project.version;/
-
- AS7 specifics - - If you have Keycloak on JBoss AS 7.1.1 you need to do few other things in order to have Keycloak up and running. This includes: - - - - Remove or comment the element from file KEYCLOAK_HOME/standalone/deployments/auth-server.war/WEB-INF/jboss-deployment-structure.xml. - - - -]]> - - - - - Then either: - - - - - - Disable webservices extension and subsystem by remove (or comment) both in KEYCLOAK_HOME/standalone/configuration/standalone.xml. - - - - - If you want to use webservices subsystem, the alternative is to remove 2 JAR files - KEYCLOAK_HOME/standalone/deployments/auth-server.war/WEB-INF/lib/keycloak-saml-protocol-&project.version;.jar - and KEYCLOAK_HOME/standalone/deployments/auth-server.war/WEB-INF/lib/picketlink-federation-&picketlink.version;.jar - but note that SAML won't work. - - - - - - - -
- AS7/EAP6.x Logging +
+ EAP6.x Logging Accessing the admin console will get these annoying log messages: @@ -452,7 +413,6 @@ keycloak-war-dist-all-&project.version;/ ]]> -
@@ -644,10 +604,10 @@ keycloak-war-dist-all-&project.version;/
- Installing the keystore to JBoss EAP6/AS7 + Installing the keystore to JBoss EAP6 Now that you have a Java keystore with the appropriate certificates, you need to configure your - JBoss EAP6/AS7 installation to use it. First step is to move the keystore file to a directory + JBoss EAP6 installation to use it. First step is to move the keystore file to a directory you can reference in configuration. I like to put it in standalone/configuration. Then you need to edit standalone/configuration/standalone.xml to enable SSL/HTTPS. @@ -713,7 +673,7 @@ keycloak-war-dist-all-&project.version;/
- AS7/EAP + EAP Open standalone/configuration/standalone.xml in your favorite editor. diff --git a/examples/demo-template/README.md b/examples/demo-template/README.md index 824b1a915a..aebd171149 100755 --- a/examples/demo-template/README.md +++ b/examples/demo-template/README.md @@ -57,6 +57,7 @@ For JBoss AS 7.1.1: Unzipping the adapter ZIP only installs the JAR files. You must also add the Keycloak Subsystem to the server's configuration (standalone/configuration/standalone.xml). +For WildFly and JBoss EAP 6.x @@ -69,6 +70,19 @@ configuration (standalone/configuration/standalone.xml). ... +For AS 7.1.1: + + + + + ... + + + + + ... + + Step 2: Boot Keycloak Server --------------------------------------- Where you go to start up the Keycloak Server depends on which distro you installed. diff --git a/integration/keycloak-as7-subsystem/pom.xml b/integration/keycloak-as7-subsystem/pom.xml new file mode 100755 index 0000000000..bbf024c247 --- /dev/null +++ b/integration/keycloak-as7-subsystem/pom.xml @@ -0,0 +1,149 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-parent + 1.1.0.Beta2-SNAPSHOT + ../../pom.xml + + + keycloak-as7-subsystem + Keycloak AS7 Subsystem + + jar + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.8.1 + + false + true + -Xmx512m + + + jboss.home + ${jboss.home} + + + + **/*TestCase.java + + once + + + + + + + + + + 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/AbstractAddStepHandlerWithAttributes.java new file mode 100755 index 0000000000..db5d1e5fc2 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java new file mode 100755 index 0000000000..a6bb63ec61 --- /dev/null +++ b/integration/keycloak-as7-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 org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +import java.util.List; + +/** + * 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java new file mode 100755 index 0000000000..681d4d9caa --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java new file mode 100755 index 0000000000..6289ff4539 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java new file mode 100755 index 0000000000..1cad10bfb0 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java new file mode 100755 index 0000000000..170aff9fa2 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -0,0 +1,139 @@ +/* + * 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.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; +import org.keycloak.subsystem.logging.KeycloakLogger; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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); + return; + } + + // else check to see if KEYCLOAK is specified as login config + WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); + if (warMetaData == null) return; + JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData(); + if (webMetaData == null) return; + + LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); + if (loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod())) { + addValve(webMetaData); + } + } + + 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); + } + addValve(webMetaData); + + LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); + if (loginConfig == null) { + loginConfig = new LoginConfigMetaData(); + webMetaData.setLoginConfig(loginConfig); + } + loginConfig.setAuthMethod("KEYCLOAK"); + loginConfig.setRealmName(service.getRealmName(deploymentName)); + KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName); + } + + private void addValve(JBossWebMetaData 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); + } + + 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java new file mode 100755 index 0000000000..eb820fcff8 --- /dev/null +++ b/integration/keycloak-as7-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 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 java.util.HashMap; +import java.util.Map; + +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(); + 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. + if (realm != null) 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java new file mode 100755 index 0000000000..c9a32d6dba --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.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.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 KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core"); + private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-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(); + + 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_JBOSS_CORE_ADAPTER, false, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java new file mode 100755 index 0000000000..a47c165742 --- /dev/null +++ b/integration/keycloak-as7-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.ResourceDefinition; +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.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 = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION); + secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); + + subsystem.registerXMLElementWriter(PARSER); + } +} diff --git a/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java new file mode 100755 index 0000000000..232532a23f --- /dev/null +++ b/integration/keycloak-as7-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 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; + +import java.util.List; + +/** + * 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java new file mode 100755 index 0000000000..d3b3f69af7 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java new file mode 100755 index 0000000000..9856e69484 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java @@ -0,0 +1,223 @@ +/* + * 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.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; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 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(RealmDefinition.TAG_NAME)) { + readRealm(reader, list); + } + else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) { + readDeployment(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 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()); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + SimpleAttributeDefinition def = RealmDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addRealm, reader); + } + + if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) { + //TODO: externalize the message + throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false."); + } + + list.add(addRealm); + } + + private void readDeployment(XMLExtendedStreamReader reader, List resourcesToAdd) throws XMLStreamException { + String name = readNameAttribute(reader); + ModelNode addSecureDeployment = new ModelNode(); + addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + 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); + } + + + /** + * TODO need to check realm-ref first. + if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) { + //TODO: externalize the message + throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false."); + } + */ + + // 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); + writeSecureDeployments(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); + } + + writer.writeEndElement(); + } + } + + private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + if (!context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).isDefined()) { + return; + } + for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java new file mode 100755 index 0000000000..df393523cf --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.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.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; + +import java.util.List; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; + +/** + * 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 (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) { + //TODO: externalize message + throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java new file mode 100755 index 0000000000..204618d176 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java @@ -0,0 +1,91 @@ +/* + * 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.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.registry.OperationEntry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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 List REALM_ONLY_ATTRIBUTES = new ArrayList(); + static { + } + + 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); + } + } + + + public static SimpleAttributeDefinition lookup(String name) { + return DEFINITION_LOOKUP.get(name); + } +} diff --git a/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java new file mode 100755 index 0000000000..a5c5441554 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java new file mode 100755 index 0000000000..1902b076bc --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.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 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; + +import java.util.List; + +/** + * 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java new file mode 100755 index 0000000000..b5b518aaad --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java @@ -0,0 +1,61 @@ +/* + * 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.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; + +import java.util.List; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; + +/** + * 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java new file mode 100755 index 0000000000..c2cb88d618 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java @@ -0,0 +1,127 @@ +/* + * 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.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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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 REALM = + new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true) + .setXmlName("realm") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + 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 SimpleAttributeDefinition PUBLIC_CLIENT = + new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true) + .setXmlName("public-client") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + + protected static final List DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); + static { + DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM); + DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE); + DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS); + DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY); + DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT); + } + + 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java new file mode 100755 index 0000000000..da0ca7e19d --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java new file mode 100755 index 0000000000..2caca6ae64 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.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 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; + +import java.util.List; + +/** + * 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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java new file mode 100755 index 0000000000..f19d4d0ee9 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java @@ -0,0 +1,227 @@ +/* + * 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.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; + +import java.util.ArrayList; +import java.util.List; + +/** + * 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 REALM_PUBLIC_KEY = + new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, true) + .setXmlName("realm-public-key") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition AUTH_SERVER_URL = + new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, true) + .setXmlName("auth-server-url") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition SSL_REQUIRED = + new SimpleAttributeDefinitionBuilder("ssl-required", ModelType.STRING, true) + .setXmlName("ssl-required") + .setAllowExpression(true) + .setDefaultValue(new ModelNode("external")) + .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 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 SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS = + new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true) + .setXmlName("auth-server-url-for-backend-requests") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN = + new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true) + .setXmlName("always-refresh-token") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP = + new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true) + .setXmlName("register-node-at-startup") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD = + new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true) + .setXmlName("register-node-period") + .setAllowExpression(true) + .setValidator(new IntRangeValidator(-1, true)) + .build(); + protected static final SimpleAttributeDefinition TOKEN_STORE = + new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true) + .setXmlName("token-store") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE = + new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true) + .setXmlName("principal-attribute") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + + + protected static final List ATTRIBUTES = new ArrayList(); + static { + ATTRIBUTES.add(REALM_PUBLIC_KEY); + ATTRIBUTES.add(AUTH_SERVER_URL); + ATTRIBUTES.add(TRUSTSTORE); + ATTRIBUTES.add(TRUSTSTORE_PASSWORD); + ATTRIBUTES.add(SSL_REQUIRED); + ATTRIBUTES.add(ALLOW_ANY_HOSTNAME); + ATTRIBUTES.add(DISABLE_TRUST_MANAGER); + ATTRIBUTES.add(CONNECTION_POOL_SIZE); + 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); + ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS); + ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN); + ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP); + ATTRIBUTES.add(REGISTER_NODE_PERIOD); + ATTRIBUTES.add(TOKEN_STORE); + ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE); + } + + /** + * truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is 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, DISABLE_TRUST_MANAGER)) { + return true; + } + + if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) { + return true; + } + + return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD); + } + + 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(); + } + + +} diff --git a/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/extension/Util.java new file mode 100755 index 0000000000..9d376e274d --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java new file mode 100755 index 0000000000..65ad2870c9 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java @@ -0,0 +1,49 @@ +/* + * 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.LogMessage; +import org.jboss.logging.Logger; +import org.jboss.logging.Message; +import org.jboss.logging.MessageLogger; + +import static org.jboss.logging.Logger.Level.DEBUG; +import static org.jboss.logging.Logger.Level.INFO; + +/** + * 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"); + + @LogMessage(level = INFO) + @Message(value = "Keycloak subsystem override for deployment %s") + void deploymentSecured(String deployment); + + @LogMessage(level = DEBUG) + @Message(value = "Keycloak has overriden and secured deployment %s") + void warSecured(String deployment); + +} diff --git a/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java b/integration/keycloak-as7-subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java new file mode 100755 index 0000000000..06a6678b26 --- /dev/null +++ b/integration/keycloak-as7-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/keycloak-as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/integration/keycloak-as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension new file mode 100755 index 0000000000..6a7d631da0 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension @@ -0,0 +1 @@ +org.keycloak.subsystem.extension.KeycloakExtension diff --git a/integration/keycloak-as7-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/integration/keycloak-as7-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties new file mode 100755 index 0000000000..e95b6cd019 --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -0,0 +1,70 @@ +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.subsystem.secure-deployment=A deployment secured by Keycloak. + +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=Public key of the realm +keycloak.realm.auth-server-url=Base URL of the Realm Auth Server +keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests +keycloak.realm.ssl-required=Specify if SSL is required (valid values are all, external and none) +keycloak.realm.allow-any-hostname=SSL Setting +keycloak.realm.truststore=Truststore used for adapter client HTTPS requests +keycloak.realm.truststore-password=Password of the Truststore +keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter +keycloak.realm.enable-cors=Enable Keycloak CORS support +keycloak.realm.client-keystore=n/a +keycloak.realm.client-keystore-password=n/a +keycloak.realm.client-key-password=n/a +keycloak.realm.cors-max-age=CORS max-age header +keycloak.realm.cors-allowed-headers=CORS allowed headers +keycloak.realm.cors-allowed-methods=CORS allowed methods +keycloak.realm.expose-token=Enable secure URL that exposes access token +keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server +keycloak.realm.always-refresh-token=Refresh token on every single web request +keycloak.realm.register-node-at-startup=Cluster setting +keycloak.realm.register-node-period=how often to re-register node +keycloak.realm.token-store=cookie or session storage for auth session data +keycloak.realm.principal-attribute=token attribute to use to set Principal name + +keycloak.secure-deployment=A deployment secured by Keycloak +keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak +keycloak.secure-deployment.realm=Keycloak realm +keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak +keycloak.secure-deployment.realm-public-key=Public key of the realm +keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server +keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests +keycloak.secure-deployment.ssl-required=Specify if SSL is required (valid values are all, external and none) +keycloak.secure-deployment.allow-any-hostname=SSL Setting +keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests +keycloak.secure-deployment.truststore-password=Password of the Truststore +keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter +keycloak.secure-deployment.resource=Application name +keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token +keycloak.secure-deployment.credentials=Adapter credentials +keycloak.secure-deployment.bearer-only=Bearer Token Auth only +keycloak.secure-deployment.public-client=Public client +keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support +keycloak.secure-deployment.client-keystore=n/a +keycloak.secure-deployment.client-keystore-password=n/a +keycloak.secure-deployment.client-key-password=n/a +keycloak.secure-deployment.cors-max-age=CORS max-age header +keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers +keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods +keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token +keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server +keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request +keycloak.secure-deployment.register-node-at-startup=Cluster setting +keycloak.secure-deployment.register-node-period=how often to re-register node +keycloak.secure-deployment.token-store=cookie or session storage for auth session data +keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name + +keycloak.secure-deployment.credential=Credential value + +keycloak.credential=Credential +keycloak.credential.value=Credential value +keycloak.credential.add=Credential add +keycloak.credential.remove=Credential remove \ No newline at end of file diff --git a/integration/keycloak-as7-subsystem/src/main/resources/schema/keycloak_1_0.xsd b/integration/keycloak-as7-subsystem/src/main/resources/schema/keycloak_1_0.xsd new file mode 100755 index 0000000000..6afc3f1bbb --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/main/resources/schema/keycloak_1_0.xsd @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + The name of the realm. + + + + + + + + + + + + The name of the deployment. + + + + + + + + + + + + + + + + + The name of the credential. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/keycloak-as7-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java b/integration/keycloak-as7-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java new file mode 100755 index 0000000000..8bae47167a --- /dev/null +++ b/integration/keycloak-as7-subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java @@ -0,0 +1,88 @@ +/* + * 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/realms/demo/protocol/openid-connect/login"); + model.get("code-url").set("http://localhost:8080/auth-server/realms/demo/protocol/openid-connect/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-required").set("none"); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("none"); + model.get("disable-trust-manager").set(false); + Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("all"); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("all"); + model.get("disable-trust-manager").set(false); + Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("external"); + model.get("disable-trust-manager").set(false); + Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("all"); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("all"); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + model.get("truststore-password").set("password"); + Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + + model.get("ssl-required").set("external"); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + model.get("truststore-password").set("password"); + Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); + } + +} diff --git a/integration/pom.xml b/integration/pom.xml index 399175b5ad..bc4b538fbd 100755 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -24,6 +24,7 @@ undertow wildfly-adapter keycloak-subsystem + keycloak-as7-subsystem js installed admin-client