keycloak-aplcache
Changes
.travis.yml 2(+1 -1)
connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java 3(+2 -1)
connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java 39(+39 -0)
distribution/adapters/pom.xml 2(+1 -1)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-subsystem/main/module.xml 2(+1 -1)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-servlet-oauth-client/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml 0(+0 -0)
distribution/adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-subsystem/main/module.xml 2(+1 -1)
distribution/demo-dist/assembly.xml 4(+2 -2)
distribution/demo-dist/pom.xml 12(+6 -6)
distribution/downloads/pom.xml 8(+4 -4)
distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-subsystem/main/module.xml 2(+1 -1)
distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wf9-subsystem/main/module.xml 4(+2 -2)
distribution/saml-adapters/pom.xml 2(+1 -1)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-spi/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-common/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-core/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-adapter-subsystem/main/module.xml 2(+1 -1)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-core/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-undertow-adapter/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-adapter/main/module.xml 0(+0 -0)
distribution/saml-adapters/wildfly-adapter/wildfly-modules/src/main/resources/modules/org/keycloak/keycloak-saml-wildfly-subsystem/main/module.xml 2(+1 -1)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/jboss/aesh/0.65/module.xml 37(+37 -0)
distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adduser/main/module.xml 15(+15 -0)
examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java 63(+34 -29)
forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties 12(+9 -3)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js 123(+64 -59)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html 30(+22 -8)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/role-selector.html 4(+2 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html 5(+3 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html 17(+17 -0)
integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 5(+3 -2)
integration/js/src/main/resources/keycloak.js 261(+198 -63)
integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java 5(+3 -2)
integration/wildfly/pom.xml 2(+1 -1)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakLogger.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakMessages.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java 0(+0 -0)
integration/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml 0(+0 -0)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java 46(+36 -10)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java 76(+54 -22)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java 12(+12 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 24(+18 -6)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 6(+6 -0)
pom.xml 12(+6 -6)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java 60(+60 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java 120(+120 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java 41(+41 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java 106(+106 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java 41(+41 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java 80(+68 -12)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java 19(+11 -8)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java 18(+10 -8)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java 13(+6 -7)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java 506(+504 -2)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java 122(+122 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java 36(+36 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java 73(+73 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java 52(+52 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java 49(+49 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java 34(+34 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java 42(+42 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java 44(+44 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java 43(+43 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java 125(+125 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java 84(+84 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java 68(+68 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java 534(+534 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java 66(+66 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties 63(+63 -0)
saml/client-adapter/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+268 -0)
saml/client-adapter/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml 49(+49 -0)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java 12(+11 -1)
saml/client-adapter/wildfly/pom.xml 2(+1 -1)
saml/client-adapter/wildfly/wildfly9-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java 91(+0 -91)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java 56(+56 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java 120(+120 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java 37(+37 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java 106(+106 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java 37(+37 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java 81(+78 -3)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java 1(+0 -1)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java 0(+0 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java 12(+8 -4)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java 1(+0 -1)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java 9(+5 -4)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java 569(+569 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java 122(+122 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java 36(+36 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java 73(+73 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java 52(+52 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java 45(+45 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java 34(+34 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java 38(+38 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java 47(+47 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java 39(+39 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java 125(+125 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java 84(+84 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java 68(+68 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 0(+0 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties 63(+63 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+268 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml 4(+2 -2)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java 56(+56 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml 50(+50 -0)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml 50(+50 -0)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java 5(+3 -2)
services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java 16(+13 -3)
testsuite/docker-cluster/pom.xml 4(+2 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java 12(+11 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java 12(+10 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java 9(+5 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java 12(+12 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecution.java 40(+40 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecutionForm.java 63(+63 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlow.java 44(+44 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlowForm.java 67(+67 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/Flows.java 90(+90 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/FlowsTable.java 93(+93 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java 42(+34 -8)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/ModalDialog.java 8(+8 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/FlowsTest.java 196(+196 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java 4(+3 -1)
Details
.travis.yml 2(+1 -1)
diff --git a/.travis.yml b/.travis.yml
index 5dee839..b2b3627 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,6 @@ install:
script:
- mvn test -B
- - mvn -file testsuite/integration-arquillian test -B
+ - mvn -file testsuite/integration-arquillian test -B -Pno-console
sudo: false
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
index eebf18d..3cf3242 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/KeycloakOIDCIdentityProvider.java
@@ -5,6 +5,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.representations.AccessTokenResponse;
@@ -51,7 +52,13 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
@POST
@Path(AdapterConstants.K_LOGOUT)
public Response backchannelLogout(String input) {
- JWSInput token = new JWSInput(input);
+ JWSInput token = null;
+ try {
+ token = new JWSInput(input);
+ } catch (JWSInputException e) {
+ logger.warn("Failed to verify logout request");
+ return Response.status(400).build();
+ }
PublicKey key = getExternalIdpKey();
if (key != null) {
if (!verify(token, key)) {
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
index 4178069..8547a03 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/AbstractClaimMapper.java
@@ -89,7 +89,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
} else if (value instanceof List) {
List list = (List)value;
for (Object val : list) {
- return valueEquals(desiredValue, val);
+ if (valueEquals(desiredValue, val)) return true;
}
}
return false;
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 4d5b45e..6938c11 100755
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -29,6 +29,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
@@ -282,40 +283,41 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
throw new IdentityBrokerException("No token from server.");
}
+ JsonWebToken token;
try {
JWSInput jws = new JWSInput(encodedToken);
if (!verify(jws, key)) {
throw new IdentityBrokerException("token signature validation failed");
}
- JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
+ token = jws.readJsonContent(JsonWebToken.class);
+ } catch (JWSInputException e) {
+ throw new IdentityBrokerException("Invalid token", e);
+ }
- String iss = token.getIssuer();
+ String iss = token.getIssuer();
- if (!token.hasAudience(getConfig().getClientId())) {
- throw new IdentityBrokerException("Wrong audience from token.");
- }
+ if (!token.hasAudience(getConfig().getClientId())) {
+ throw new IdentityBrokerException("Wrong audience from token.");
+ }
- if (!token.isActive()) {
- throw new IdentityBrokerException("Token is no longer valid");
- }
+ if (!token.isActive()) {
+ throw new IdentityBrokerException("Token is no longer valid");
+ }
- String trustedIssuers = getConfig().getIssuer();
+ String trustedIssuers = getConfig().getIssuer();
- if (trustedIssuers != null) {
- String[] issuers = trustedIssuers.split(",");
+ if (trustedIssuers != null) {
+ String[] issuers = trustedIssuers.split(",");
- for (String trustedIssuer : issuers) {
- if (iss != null && iss.equals(trustedIssuer.trim())) {
- return token;
- }
+ for (String trustedIssuer : issuers) {
+ if (iss != null && iss.equals(trustedIssuer.trim())) {
+ return token;
}
-
- throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
- return token;
- } catch (IOException e) {
- throw new IdentityBrokerException("Could not decode token.", e);
+
+ throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
}
+ return token;
}
@Override
diff --git a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
index 328916e..35085c7 100755
--- a/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
+++ b/common/src/main/java/org/keycloak/common/util/KeycloakUriBuilder.java
@@ -342,6 +342,17 @@ public class KeycloakUriBuilder {
}
/**
+ * Set fragment, but not encode it. It assumes that given fragment was already properly encoded
+ *
+ * @param fragment
+ * @return
+ */
+ public KeycloakUriBuilder encodedFragment(String fragment) {
+ this.fragment = fragment;
+ return this;
+ }
+
+ /**
* Only replace path params in path of URI. This changes state of URIBuilder.
*
* @param name
diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
index c81b062..eadbdcf 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml
@@ -51,6 +51,11 @@
<constraints nullable="false"/>
</column>
</addColumn>
+
+ <addColumn tableName="REALM">
+ <column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
+ </addColumn>
+
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
@@ -69,11 +74,37 @@
<addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
+
<addColumn tableName="CLIENT">
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
+ <column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
+ <constraints nullable="false"/>
+ </column>
+ <column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
+ <constraints nullable="false"/>
+ </column>
</addColumn>
+ <update tableName="CLIENT">
+ <column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
+ <where>DIRECT_GRANTS_ONLY = :value</where>
+ <whereParams>
+ <param valueBoolean="true" />
+ </whereParams>
+ </update>
+
+ <dropDefaultValue tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY" />
+ <dropColumn tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY"/>
+
<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
+ <!-- Sybase specific hacks -->
+ <modifySql dbms="sybase">
+ <regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
+ </modifySql>
+
</changeSet>
</databaseChangeLog>
\ No newline at end of file
diff --git a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
index fa75d6b..e66e16d 100644
--- a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
+++ b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java
@@ -28,7 +28,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
Update1_2_0_Beta1.class,
Update1_2_0_CR1.class,
Update1_3_0.class,
- Update1_4_0.class
+ Update1_4_0.class,
+ Update1_7_0.class
};
@Override
diff --git a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java
new file mode 100644
index 0000000..666a3af
--- /dev/null
+++ b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java
@@ -0,0 +1,39 @@
+package org.keycloak.connections.mongo.updater.impl.updates;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBCollection;
+import com.mongodb.DBCursor;
+import org.keycloak.models.KeycloakSession;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Update1_7_0 extends Update {
+
+ @Override
+ public String getId() {
+ return "1.7.0";
+ }
+
+ @Override
+ public void update(KeycloakSession session) throws ClassNotFoundException {
+ DBCollection clients = db.getCollection("clients");
+ DBCursor clientsCursor = clients.find();
+
+ try {
+ while (clientsCursor.hasNext()) {
+ BasicDBObject client = (BasicDBObject) clientsCursor.next();
+
+ boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
+ client.append("standardFlowEnabled", !directGrantsOnly);
+ client.append("implicitFlowEnabled", false);
+ client.append("directAccessGrantsEnabled", true);
+ client.removeField("directGrantsOnly");
+
+ clients.save(client);
+ }
+ } finally {
+ clientsCursor.close();
+ }
+ }
+}
diff --git a/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java b/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
index 59e25ee..dc0e4e3 100755
--- a/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jws/crypto/RSAProvider.java
@@ -64,7 +64,7 @@ public class RSAProvider implements SignatureProvider {
verifier.update(input.getEncodedSignatureInput().getBytes("UTF-8"));
return verifier.verify(input.getSignature());
} catch (Exception e) {
- throw new RuntimeException(e);
+ return false;
}
}
diff --git a/core/src/main/java/org/keycloak/jose/jws/JWSInput.java b/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
index b01355f..84f303d 100755
--- a/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
+++ b/core/src/main/java/org/keycloak/jose/jws/JWSInput.java
@@ -21,14 +21,14 @@ public class JWSInput {
byte[] signature;
- public JWSInput(String wire) {
- this.wireString = wire;
- String[] parts = wire.split("\\.");
- if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
- encodedHeader = parts[0];
- encodedContent = parts[1];
- encodedSignatureInput = encodedHeader + '.' + encodedContent;
+ public JWSInput(String wire) throws JWSInputException {
try {
+ this.wireString = wire;
+ String[] parts = wire.split("\\.");
+ if (parts.length < 2 || parts.length > 3) throw new IllegalArgumentException("Parsing error");
+ encodedHeader = parts[0];
+ encodedContent = parts[1];
+ encodedSignatureInput = encodedHeader + '.' + encodedContent;
content = Base64Url.decode(encodedContent);
if (parts.length > 2) {
encodedSignature = parts[2];
@@ -37,8 +37,8 @@ public class JWSInput {
}
byte[] headerBytes = Base64Url.decode(encodedHeader);
header = JsonSerialization.readValue(headerBytes, JWSHeader.class);
- } catch (Exception e) {
- throw new RuntimeException(e);
+ } catch (Throwable t) {
+ throw new JWSInputException(t);
}
}
@@ -80,8 +80,12 @@ public class JWSInput {
return header.getAlgorithm().getProvider().verify(this, key);
}
- public <T> T readJsonContent(Class<T> type) throws IOException {
- return JsonSerialization.readValue(content, type);
+ public <T> T readJsonContent(Class<T> type) throws JWSInputException {
+ try {
+ return JsonSerialization.readValue(content, type);
+ } catch (IOException e) {
+ throw new JWSInputException(e);
+ }
}
public String readContentAsString() {
diff --git a/core/src/main/java/org/keycloak/jose/jws/JWSInputException.java b/core/src/main/java/org/keycloak/jose/jws/JWSInputException.java
new file mode 100644
index 0000000..930a0b6
--- /dev/null
+++ b/core/src/main/java/org/keycloak/jose/jws/JWSInputException.java
@@ -0,0 +1,18 @@
+package org.keycloak.jose.jws;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JWSInputException extends Exception {
+
+ public JWSInputException(String s) {
+ super(s);
+ }
+
+ public JWSInputException() {
+ }
+
+ public JWSInputException(Throwable throwable) {
+ super(throwable);
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
index 514c0fb..aef643c 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -26,6 +26,9 @@ public class ClientRepresentation {
protected Integer notBefore;
protected Boolean bearerOnly;
protected Boolean consentRequired;
+ protected Boolean standardFlowEnabled;
+ protected Boolean implicitFlowEnabled;
+ protected Boolean directAccessGrantsEnabled;
protected Boolean serviceAccountsEnabled;
protected Boolean directGrantsOnly;
protected Boolean publicClient;
@@ -181,6 +184,30 @@ public class ClientRepresentation {
this.consentRequired = consentRequired;
}
+ public Boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public void setStandardFlowEnabled(Boolean standardFlowEnabled) {
+ this.standardFlowEnabled = standardFlowEnabled;
+ }
+
+ public Boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public void setImplicitFlowEnabled(Boolean implicitFlowEnabled) {
+ this.implicitFlowEnabled = implicitFlowEnabled;
+ }
+
+ public Boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
+ public void setDirectAccessGrantsEnabled(Boolean directAccessGrantsEnabled) {
+ this.directAccessGrantsEnabled = directAccessGrantsEnabled;
+ }
+
public Boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 9b76490..bc9ebf8 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -12,6 +12,7 @@ public class RealmRepresentation {
protected Integer notBefore;
protected Boolean revokeRefreshToken;
protected Integer accessTokenLifespan;
+ protected Integer accessTokenLifespanForImplicitFlow;
protected Integer ssoSessionIdleTimeout;
protected Integer ssoSessionMaxLifespan;
protected Integer offlineSessionIdleTimeout;
@@ -84,6 +85,8 @@ public class RealmRepresentation {
private List<IdentityProviderRepresentation> identityProviders;
private List<IdentityProviderMapperRepresentation> identityProviderMappers;
private List<ProtocolMapperRepresentation> protocolMappers;
+ @Deprecated
+ private Boolean identityFederationEnabled;
protected Boolean internationalizationEnabled;
protected Set<String> supportedLocales;
protected String defaultLocale;
@@ -185,6 +188,14 @@ public class RealmRepresentation {
this.accessTokenLifespan = accessTokenLifespan;
}
+ public Integer getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
+ public void setAccessTokenLifespanForImplicitFlow(Integer accessTokenLifespanForImplicitFlow) {
+ this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+ }
+
public Integer getSsoSessionIdleTimeout() {
return ssoSessionIdleTimeout;
}
@@ -612,6 +623,11 @@ public class RealmRepresentation {
identityProviders.add(identityProviderRepresentation);
}
+ @Deprecated
+ public boolean isIdentityFederationEnabled() {
+ return identityProviders != null && !identityProviders.isEmpty();
+ }
+
public List<ProtocolMapperRepresentation> getProtocolMappers() {
return protocolMappers;
}
diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java
index 39c7c46..0300673 100755
--- a/core/src/main/java/org/keycloak/representations/RefreshToken.java
+++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java
@@ -28,6 +28,7 @@ public class RefreshToken extends AccessToken {
this.subject = token.subject;
this.issuedFor = token.issuedFor;
this.sessionState = token.sessionState;
+ this.nonce = token.nonce;
if (token.realmAccess != null) {
realmAccess = token.realmAccess.clone();
}
diff --git a/core/src/main/java/org/keycloak/RSATokenVerifier.java b/core/src/main/java/org/keycloak/RSATokenVerifier.java
index f177166..19babef 100755
--- a/core/src/main/java/org/keycloak/RSATokenVerifier.java
+++ b/core/src/main/java/org/keycloak/RSATokenVerifier.java
@@ -2,6 +2,7 @@ package org.keycloak;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.AccessToken;
import org.keycloak.util.TokenUtil;
@@ -22,7 +23,7 @@ public class RSATokenVerifier {
JWSInput input = null;
try {
input = new JWSInput(tokenString);
- } catch (Exception e) {
+ } catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token", e);
}
if (!isPublicKeyValid(input, realmKey)) throw new VerificationException("Invalid token signature.");
@@ -30,7 +31,7 @@ public class RSATokenVerifier {
AccessToken token;
try {
token = input.readJsonContent(AccessToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new VerificationException("Couldn't parse token signature", e);
}
String user = token.getSubject();
diff --git a/core/src/main/java/org/keycloak/util/TokenUtil.java b/core/src/main/java/org/keycloak/util/TokenUtil.java
index 10b6c20..94f6a73 100644
--- a/core/src/main/java/org/keycloak/util/TokenUtil.java
+++ b/core/src/main/java/org/keycloak/util/TokenUtil.java
@@ -4,6 +4,7 @@ import java.io.IOException;
import org.keycloak.OAuth2Constants;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.RefreshToken;
/**
@@ -41,11 +42,15 @@ public class TokenUtil {
* @param decodedToken
* @return
*/
- public static RefreshToken getRefreshToken(byte[] decodedToken) throws IOException {
- return JsonSerialization.readValue(decodedToken, RefreshToken.class);
+ public static RefreshToken getRefreshToken(byte[] decodedToken) throws JWSInputException {
+ try {
+ return JsonSerialization.readValue(decodedToken, RefreshToken.class);
+ } catch (IOException e) {
+ throw new JWSInputException(e);
+ }
}
- public static RefreshToken getRefreshToken(String refreshToken) throws IOException {
+ public static RefreshToken getRefreshToken(String refreshToken) throws JWSInputException {
byte[] encodedContent = new JWSInput(refreshToken).getContent();
return getRefreshToken(encodedContent);
}
@@ -56,13 +61,9 @@ public class TokenUtil {
* @param refreshToken
* @return
*/
- public static boolean isOfflineToken(String refreshToken) {
- try {
- RefreshToken token = getRefreshToken(refreshToken);
- return token.getType().equals(TOKEN_TYPE_OFFLINE);
- } catch (IOException ioe) {
- throw new RuntimeException(ioe);
- }
+ public static boolean isOfflineToken(String refreshToken) throws JWSInputException {
+ RefreshToken token = getRefreshToken(refreshToken);
+ return token.getType().equals(TOKEN_TYPE_OFFLINE);
}
}
distribution/adapters/pom.xml 2(+1 -1)
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index 10c8e0f..d13d992 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -25,6 +25,6 @@
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>wf8-adapter</module>
- <module>wf9-adapter</module>
+ <module>wildfly-adapter</module>
</modules>
</project>
distribution/demo-dist/assembly.xml 4(+2 -2)
diff --git a/distribution/demo-dist/assembly.xml b/distribution/demo-dist/assembly.xml
index f1c39c2..bd169bf 100755
--- a/distribution/demo-dist/assembly.xml
+++ b/distribution/demo-dist/assembly.xml
@@ -33,14 +33,14 @@
</excludes>
</fileSet>
<fileSet>
- <directory>${project.build.directory}/unpacked/keycloak-wf9-adapter-${project.version}</directory>
+ <directory>${project.build.directory}/unpacked/keycloak-wildfly-adapter-${project.version}</directory>
<outputDirectory>keycloak</outputDirectory>
<excludes>
<exclude>standalone/configuration/standalone-keycloak.xml</exclude>
</excludes>
</fileSet>
<fileSet>
- <directory>${project.build.directory}/unpacked/keycloak-saml-wf9-adapter-${project.version}</directory>
+ <directory>${project.build.directory}/unpacked/keycloak-saml-wildfly-adapter-${project.version}</directory>
<outputDirectory>keycloak</outputDirectory>
<excludes>
<exclude>standalone/configuration/standalone-keycloak.xml</exclude>
distribution/demo-dist/pom.xml 12(+6 -6)
diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml
index 9443478..2caeb2b 100755
--- a/distribution/demo-dist/pom.xml
+++ b/distribution/demo-dist/pom.xml
@@ -21,12 +21,12 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
<dependency>
@@ -99,9 +99,9 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
- <outputDirectory>${project.build.directory}/unpacked/keycloak-wf9-adapter-${project.version}</outputDirectory>
+ <outputDirectory>${project.build.directory}/unpacked/keycloak-wildfly-adapter-${project.version}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
@@ -116,9 +116,9 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
- <outputDirectory>${project.build.directory}/unpacked/keycloak-saml-wf9-adapter-${project.version}</outputDirectory>
+ <outputDirectory>${project.build.directory}/unpacked/keycloak-saml-wildfly-adapter-${project.version}</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl
index 106c08f..9f52547 100755
--- a/distribution/demo-dist/src/main/xslt/standalone.xsl
+++ b/distribution/demo-dist/src/main/xslt/standalone.xsl
@@ -44,7 +44,7 @@
<web-context>auth</web-context>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak:1.1"/>
- <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.6"/>
+ <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>
</xsl:copy>
</xsl:template>
distribution/downloads/pom.xml 8(+4 -4)
diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml
index b9daadf..b742897 100755
--- a/distribution/downloads/pom.xml
+++ b/distribution/downloads/pom.xml
@@ -239,12 +239,12 @@
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>tar.gz</type>
</artifactItem>
</artifactItems>
@@ -338,12 +338,12 @@
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>zip</type>
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<type>tar.gz</type>
</artifactItem>
</artifactItems>
diff --git a/distribution/feature-packs/adapter-feature-pack/pom.xml b/distribution/feature-packs/adapter-feature-pack/pom.xml
index 6d1a7a0..8fe0163 100755
--- a/distribution/feature-packs/adapter-feature-pack/pom.xml
+++ b/distribution/feature-packs/adapter-feature-pack/pom.xml
@@ -34,7 +34,7 @@
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-subsystem</artifactId>
+ <artifactId>keycloak-wildfly-subsystem</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-subsystem/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-subsystem/main/module.xml
index 66278bd..f759d30 100644
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-subsystem/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-adapter-subsystem/main/module.xml
@@ -28,6 +28,6 @@
</resources>
<dependencies>
- <module name="org.keycloak.keycloak-wf9-subsystem" export="true" services="export"/>
+ <module name="org.keycloak.keycloak-wildfly-subsystem" export="true" services="export"/>
</dependencies>
</module>
diff --git a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wf9-subsystem/main/module.xml b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wf9-subsystem/main/module.xml
index 4b2efe5..732e674 100755
--- a/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wf9-subsystem/main/module.xml
+++ b/distribution/feature-packs/adapter-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-wf9-subsystem/main/module.xml
@@ -22,10 +22,10 @@
~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
-->
-<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wf9-subsystem">
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-subsystem">
<resources>
- <artifact name="${org.keycloak:keycloak-wf9-subsystem}"/>
+ <artifact name="${org.keycloak:keycloak-wildfly-subsystem}"/>
</resources>
<dependencies>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
index 1acb6aa..78c4823 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/as7-adapter-zip/assembly.xml
@@ -29,7 +29,7 @@
</fileSets>
<files>
<file>
- <source>../../shared-cli/adapter-install.cli</source>
+ <source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
diff --git a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
index f844a41..1ed0089 100755
--- a/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
+++ b/distribution/saml-adapters/as7-eap6-adapter/eap6-adapter-zip/assembly.xml
@@ -29,7 +29,7 @@
</fileSets>
<files>
<file>
- <source>../../shared-cli/adapter-install.cli</source>
+ <source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
distribution/saml-adapters/pom.xml 2(+1 -1)
diff --git a/distribution/saml-adapters/pom.xml b/distribution/saml-adapters/pom.xml
index 456f292..ea95663 100755
--- a/distribution/saml-adapters/pom.xml
+++ b/distribution/saml-adapters/pom.xml
@@ -15,7 +15,7 @@
<packaging>pom</packaging>
<modules>
- <module>wf9-adapter</module>
+ <module>wildfly-adapter</module>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
index e8086dc..09be9a4 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml
@@ -71,6 +71,10 @@
<maven-resource group="com.google.zxing" artifact="core"/>
</module-def>
+ <module-def name="org.jboss.aesh" slot="0.65">
+ <maven-resource group="org.jboss.aesh" artifact="aesh"/>
+ </module-def>
+
<module-def name="com.google.zxing.javase">
<maven-resource group="com.google.zxing" artifact="javase"/>
</module-def>
@@ -246,6 +250,10 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
</module-def>
+ <module-def name="org.keycloak.keycloak-wildfly-adduser">
+ <maven-resource group="org.keycloak" artifact="keycloak-wildfly-adduser"/>
+ </module-def>
+
<!-- mongo -->
<module-def name="org.keycloak.keycloak-connections-mongo">
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
index b3225fd..1d5d272 100755
--- a/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
+++ b/distribution/server-overlay/eap6/eap6-server-modules/pom.xml
@@ -39,6 +39,10 @@
<artifactId>keycloak-wildfly-extensions</artifactId>
</dependency>
<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adduser</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
@@ -46,6 +50,10 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.jboss.aesh</groupId>
+ <artifactId>aesh</artifactId>
+ </dependency>
</dependencies>
<build>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/jboss/aesh/0.65/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/jboss/aesh/0.65/module.xml
new file mode 100644
index 0000000..8a9d6f8
--- /dev/null
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/jboss/aesh/0.65/module.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ JBoss, Home of Professional Open Source.
+ ~ Copyright 2010, Red Hat, Inc., and individual contributors
+ ~ as indicated by the @author tags. See the copyright.txt file in the
+ ~ distribution for a full listing of individual contributors.
+ ~
+ ~ This is free software; you can redistribute it and/or modify it
+ ~ under the terms of the GNU Lesser General Public License as
+ ~ published by the Free Software Foundation; either version 2.1 of
+ ~ the License, or (at your option) any later version.
+ ~
+ ~ This software is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ~ Lesser General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public
+ ~ License along with this software; if not, write to the Free
+ ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ -->
+
+<module xmlns="urn:jboss:module:1.3" name="org.jboss.aesh" slot="0.65">
+ <properties>
+ <property name="jboss.api" value="private"/>
+ </properties>
+
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+
+ <dependencies>
+ <module name="org.fusesource.jansi" />
+ </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adduser/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adduser/main/module.xml
new file mode 100755
index 0000000..e781d10
--- /dev/null
+++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adduser/main/module.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-wildfly-adduser">
+ <main-class name="org.keycloak.wildfly.adduser.AddUser"/>
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="org.keycloak.keycloak-common"/>
+ <module name="org.keycloak.keycloak-core"/>
+ <module name="org.keycloak.keycloak-model-api"/>
+ <module name="org.jboss.aesh" slot="0.65"/>
+ <module name="org.jboss.as.domain-management"/>
+ <module name="org.codehaus.jackson.jackson-core-asl"/>
+ </dependencies>
+</module>
diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
index b3324df..8510f2b 100755
--- a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
+++ b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml
@@ -52,6 +52,11 @@
<source>src/main/providers/README.txt</source>
<outputDirectory>standalone/configuration/providers</outputDirectory>
</file>
+ <file>
+ <source>../../../feature-packs/server-feature-pack/src/main/resources/content/bin/add-user.sh</source>
+ <outputDirectory>bin</outputDirectory>
+ <destName>add-user-keycloak.sh</destName>
+ </file>
</files>
</assembly>
diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml
index 8f0daa2..5b96e2d 100755
--- a/distribution/server-overlay/wf9-server-overlay/assembly.xml
+++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml
@@ -15,6 +15,7 @@
<includes>
<include>com/google/zxing/**</include>
<include>org/freemarker/**</include>
+ <include>org/jboss/aesh/**</include>
<include>org/keycloak/**</include>
<include>org/liquibase/**</include>
<include>org/mongodb/**</include>
@@ -71,6 +72,11 @@
<source>${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json</source>
<outputDirectory>standalone/configuration</outputDirectory>
</file>
+ <file>
+ <source>${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user.sh</source>
+ <outputDirectory>bin</outputDirectory>
+ <destName>add-user-keycloak.sh</destName>
+ </file>
</files>
</assembly>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
index 71e599d..7524f1d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/jboss-adapter.xml
@@ -14,10 +14,10 @@
the Keycloak download site. They are also available as a maven artifact.
</para>
<para>
- Install on Wildfly 9:
+ Install on Wildfly 9 or 10:
<programlisting>
$ cd $WILDFLY_HOME
-$ unzip keycloak-wf9-adapter-dist.zip
+$ unzip keycloak-wildfly-adapter-dist.zip
</programlisting>
</para>
<para>
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
index 7f8800e..229ee8d 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml
@@ -43,6 +43,12 @@
application not knowing if the user's permissions have changed. This value is usually in minutes.
</para>
<para>
+ The <code class="literal">Access Token Lifespan For Implicit Flow</code> is how long an access token is valid for when using OpenID Connect implicit flow.
+ With implicit flow, there is no refresh token available, so that's why the lifespan is usually bigger than default Access Token Lifespan mentioned above.
+ See <ulink url="http://openid.net/specs/openid-connect-core-1_0.html#ImplicitFlowAuth">OpenID Connect specification</ulink> for details about implicit flow and
+ <link linkend="javascript-adapter">Javascript Adapter</link> for some additional details.
+ </para>
+ <para>
The <literal>Client login timeout</literal> is how long an access code is valid for. An access code is obtained
on the 1st leg of the OAuth 2.0 redirection protocol. This should be a short time limit. Usually seconds.
</para>
diff --git a/docbook/saml-adapter-docs/reference/en/en-US/modules/jboss-adapter.xml b/docbook/saml-adapter-docs/reference/en/en-US/modules/jboss-adapter.xml
index fc29402..675cc0b 100755
--- a/docbook/saml-adapter-docs/reference/en/en-US/modules/jboss-adapter.xml
+++ b/docbook/saml-adapter-docs/reference/en/en-US/modules/jboss-adapter.xml
@@ -3,7 +3,7 @@
<para>
To be able to secure WAR apps deployed on JBoss EAP 6.x or Wildfly, you must install and
configure the Keycloak SAML Adapter Subsystem. You then provide a keycloak
- config, <literal>/WEB-INF/keycloak-saml</literal> file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml.
+ config, <literal>/WEB-INF/keycloak-saml.xml</literal> file in your WAR and change the auth-method to KEYCLOAK-SAML within web.xml.
Both methods are described in this section.
</para>
<section id="jboss-adapter-installation">
@@ -13,10 +13,10 @@
the Keycloak download site. They are also available as a maven artifact.
</para>
<para>
- Install on Wildfly 9:
+ Install on Wildfly 9 or 10:
<programlisting>
$ cd $WILDFLY_HOME
-$ unzip keycloak-saml-wf9-adapter-dist.zip
+$ unzip keycloak-saml-wildfly-adapter-dist.zip
</programlisting>
</para>
<para>
@@ -38,7 +38,7 @@ $ unzip keycloak-saml-eap6-adapter-dist.zip
from the server's bin directory:
<programlisting>
$ cd $JBOSS_HOME/bin
-$ jboss-cli.sh -c --file=adapter-install.cli
+$ jboss-cli.sh -c --file=adapter-install-saml.cli
</programlisting>
The script will add the extension, subsystem, and optional security-domain as described below.
</para>
@@ -52,7 +52,7 @@ $ jboss-cli.sh -c --file=adapter-install.cli
</extensions>
<profile>
- <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.6"/>
+ <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1"/>
...
</profile>
]]>
@@ -185,4 +185,92 @@ public class CustomerService {
</programlisting>
</para>
</section>
+ <section>
+ <title>Securing WARs via Keycloak SAML Subsystem</title>
+ <para>
+ You do not have to crack open a WAR to secure it with Keycloak. Alternatively, you can externally secure
+ it via the Keycloak SAML Adapter Subsystem. While you don't have to specify KEYCLOAK-SAML as an <literal>auth-method</literal>,
+ you still have to define the <literal>security-constraints</literal> in <literal>web.xml</literal>. You do
+ not, however, have to create a <literal>WEB-INF/keycloak-saml.xml</literal> file. This metadata is instead defined
+ within XML in your server's <literal>domain.xml</literal> or <literal>standalone.xml</literal> subsystem
+ configuration section.
+ </para>
+ <para>
+ <programlisting><![CDATA[
+<extensions>
+ <extension module="org.keycloak.keycloak-saml-adapter-subsystem"/>
+</extensions>
+
+<profile>
+ <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ <secure-deployment name="WAR MODULE NAME.war">
+ <SP entityID="APPLICATION URL">
+ ...
+ </SP>
+ </secure-deployment>
+ </subsystem>
+</profile>
+]]>
+ </programlisting>
+ </para>
+ <para>
+ The <literal>secure-deployment</literal> <literal>name</literal> attribute identifies the WAR you want
+ to secure. Its value is the <literal>module-name</literal> defined in <literal>web.xml</literal> with
+ <literal>.war</literal> appended. The rest of the configuration uses the same XML syntax as
+ <literal>keycloak-saml.xml</literal> configuration defined in <link linkend='adapter-config'>general adapter configuration</link>.
+ </para>
+ <para>
+ An example configuration:
+ </para>
+ <para>
+ <programlisting><![CDATA[
+<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ <secure-deployment name="saml-post-encryption.war">
+ <SP entityID="http://localhost:8080/sales-post-enc/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+ <Keys>
+ <Key signing="true" encryption="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <Keys>
+ <Key signing="true" >
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="saml-demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+ </secure-deployment>
+</subsystem>
+]]>
+ </programlisting>
+ </para>
+ </section>
</chapter>
\ No newline at end of file
diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java
index b9c5338..a995d7b 100755
--- a/events/api/src/main/java/org/keycloak/events/Details.java
+++ b/events/api/src/main/java/org/keycloak/events/Details.java
@@ -11,6 +11,7 @@ public interface Details {
String CODE_ID = "code_id";
String REDIRECT_URI = "redirect_uri";
String RESPONSE_TYPE = "response_type";
+ String RESPONSE_MODE = "response_mode";
String AUTH_TYPE = "auth_type";
String AUTH_METHOD = "auth_method";
String IDENTITY_PROVIDER = "identity_provider";
diff --git a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
index 1143968..2583ec4 100755
--- a/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
+++ b/examples/demo-template/offline-access-app/src/main/java/org/keycloak/example/OfflineAccessPortalServlet.java
@@ -23,6 +23,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.RefreshToken;
import org.keycloak.util.JsonSerialization;
@@ -49,40 +50,44 @@ public class OfflineAccessPortalServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ try {
+ if (req.getRequestURI().endsWith("/login")) {
+ storeToken(req);
+ req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
+ return;
+ }
- if (req.getRequestURI().endsWith("/login")) {
- storeToken(req);
- req.getRequestDispatcher("/WEB-INF/pages/loginCallback.jsp").forward(req, resp);
- return;
- }
+ String refreshToken = RefreshTokenDAO.loadToken();
+ String refreshTokenInfo;
+ boolean savedTokenAvailable;
+ if (refreshToken == null) {
+ refreshTokenInfo = "No token saved in database. Please login first";
+ savedTokenAvailable = false;
+ } else {
+ RefreshToken refreshTokenDecoded = null;
+ refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
+ String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
+ refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
+ savedTokenAvailable = true;
+ }
+ req.setAttribute("tokenInfo", refreshTokenInfo);
+ req.setAttribute("savedTokenAvailable", savedTokenAvailable);
- String refreshToken = RefreshTokenDAO.loadToken();
- String refreshTokenInfo;
- boolean savedTokenAvailable;
- if (refreshToken == null) {
- refreshTokenInfo = "No token saved in database. Please login first";
- savedTokenAvailable = false;
- } else {
- RefreshToken refreshTokenDecoded = TokenUtil.getRefreshToken(refreshToken);
- String exp = (refreshTokenDecoded.getExpiration() == 0) ? "NEVER" : Time.toDate(refreshTokenDecoded.getExpiration()).toString();
- refreshTokenInfo = String.format("<p>Type: %s</p><p>ID: %s</p><p>Expires: %s</p>", refreshTokenDecoded.getType(), refreshTokenDecoded.getId(), exp);
- savedTokenAvailable = true;
- }
- req.setAttribute("tokenInfo", refreshTokenInfo);
- req.setAttribute("savedTokenAvailable", savedTokenAvailable);
-
- String customers;
- if (req.getRequestURI().endsWith("/loadCustomers")) {
- customers = loadCustomers(req, refreshToken);
- } else {
- customers = "";
- }
- req.setAttribute("customers", customers);
+ String customers;
+ if (req.getRequestURI().endsWith("/loadCustomers")) {
+ customers = loadCustomers(req, refreshToken);
+ } else {
+ customers = "";
+ }
+ req.setAttribute("customers", customers);
- req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
+ req.getRequestDispatcher("/WEB-INF/pages/view.jsp").forward(req, resp);
+ } catch (JWSInputException e) {
+ throw new ServletException(e);
+ }
}
- private void storeToken(HttpServletRequest req) throws IOException {
+ private void storeToken(HttpServletRequest req) throws IOException, JWSInputException {
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String refreshToken = ctx.getRefreshToken();
diff --git a/examples/js-console/src/main/webapp/index.html b/examples/js-console/src/main/webapp/index.html
index 2cca0f8..3789a9f 100644
--- a/examples/js-console/src/main/webapp/index.html
+++ b/examples/js-console/src/main/webapp/index.html
@@ -67,8 +67,11 @@
var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n';
- o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
- o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
+ if (keycloak.refreshTokenParsed) {
+ o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n';
+ o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds';
+ }
+
output(o);
}
@@ -106,7 +109,17 @@
event('Auth Logout');
};
- keycloak.init().success(function(authenticated) {
+ keycloak.onTokenExpired = function () {
+ event('Access token expired.');
+ };
+
+ // Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
+ var initOptions = {
+ responseMode: 'fragment',
+ flow: 'standard'
+ };
+
+ keycloak.init(initOptions).success(function(authenticated) {
output('Init Success (' + (authenticated ? 'Authenticated' : 'Not Authenticated') + ')');
}).error(function() {
output('Init Error');
diff --git a/forms/common-themes/src/main/resources/theme/base/account/password.ftl b/forms/common-themes/src/main/resources/theme/base/account/password.ftl
index c142e79..053fdbd 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/password.ftl
+++ b/forms/common-themes/src/main/resources/theme/base/account/password.ftl
@@ -49,7 +49,6 @@
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
- <button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
</div>
</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index bf1af10..b92f65b 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -80,6 +80,8 @@ offline-session-idle=Offline Session Idle
offline-session-idle.tooltip=Time an offline session is allowed to be idle before it expires. You need to use offline token to refresh at least once within this period, otherwise offline session will expire.
access-token-lifespan=Access Token Lifespan
access-token-lifespan.tooltip=Max time before an access token is expired. This value is recommended to be short relative to the SSO timeout.
+access-token-lifespan-for-implicit-flow=Access Token Lifespan For Implicit Flow
+access-token-lifespan-for-implicit-flow.tooltip=Max time before an access token issued during OpenID Connect Implicit Flow is expired. This value is recommended to be shorter than SSO timeout. There is no possibility to refresh token during implicit flow, that's why there is separate timeout different to 'Access Token Lifespan'.
client-login-timeout=Client login timeout
client-login-timeout.tooltip=Max time an client has to finish the access token protocol. This should normally be 1 minute.
login-timeout=Login timeout
@@ -169,14 +171,18 @@ client.name.tooltip=Specifies display name of the client. For example 'My Client
client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens.
consent-required=Consent Required
consent-required.tooltip=If enabled users have to consent to client access.
-direct-grants-only=Direct Grants Only
-direct-grants-only.tooltip=When enabled, client can only obtain grants from grant REST API.
client-protocol=Client Protocol
client-protocol.tooltip='OpenID connect' allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server.'SAML' enables web-based authentication and authorization scenarios including cross-domain single sign-on (SSO) and uses security tokens containing assertions to pass information.
access-type=Access Type
access-type.tooltip='Confidential' clients require a secret to initiate login protocol. 'Public' clients do not require a secret. 'Bearer-only' clients are web services that never initiate a login.
+standard-flow-enabled=Standard Flow Enabled
+standard-flow-enabled.tooltip=This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.
+implicit-flow-enabled=Implicit Flow Enabled
+implicit-flow-enabled.tooltip=This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.
+direct-access-grants-enabled=Direct Access Grants Enabled
+direct-access-grants-enabled.tooltip=This enables support for Direct Access Grants, which means that client has access to username/password of user and exchange it directly with Keycloak server for access token. In terms of OAuth2 specification, this enables support of 'Resource Owner Password Credentials Grant' for this client.
service-accounts-enabled=Service Accounts Enabled
-service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client.
+service-accounts-enabled.tooltip=Allows you to authenticate this client to Keycloak and retrieve access token dedicated to this client. In terms of OAuth2 specification, this enables support of 'Client Credentials Grant' for this client.
include-authnstatement=Include AuthnStatement
include-authnstatement.tooltip=Should a statement specifying the method and timestamp be included in login responses?
sign-documents=Sign Documents
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
index 971b0c4..11bb11b 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js
@@ -799,13 +799,75 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
} else if ($scope.client.attributes['saml_name_id_format'] == 'persistent') {
$scope.nameIdFormat = $scope.nameIdFormats[3];
}
+ if ($scope.client.attributes["saml.server.signature"]) {
+ if ($scope.client.attributes["saml.server.signature"] == "true") {
+ $scope.samlServerSignature = true;
+ } else {
+ $scope.samlServerSignature = false;
+
+ }
+ }
+ if ($scope.client.attributes["saml.assertion.signature"]) {
+ if ($scope.client.attributes["saml.assertion.signature"] == "true") {
+ $scope.samlAssertionSignature = true;
+ } else {
+ $scope.samlAssertionSignature = false;
+ }
+ }
+ if ($scope.client.attributes["saml.client.signature"]) {
+ if ($scope.client.attributes["saml.client.signature"] == "true") {
+ $scope.samlClientSignature = true;
+ } else {
+ $scope.samlClientSignature = false;
+ }
+ }
+ if ($scope.client.attributes["saml.encrypt"]) {
+ if ($scope.client.attributes["saml.encrypt"] == "true") {
+ $scope.samlEncrypt = true;
+ } else {
+ $scope.samlEncrypt = false;
+ }
+ }
+ if ($scope.client.attributes["saml.authnstatement"]) {
+ if ($scope.client.attributes["saml.authnstatement"] == "true") {
+ $scope.samlAuthnStatement = true;
+ } else {
+ $scope.samlAuthnStatement = false;
+ }
+ }
+ if ($scope.client.attributes["saml_force_name_id_format"]) {
+ if ($scope.client.attributes["saml_force_name_id_format"] == "true") {
+ $scope.samlForceNameIdFormat = true;
+ } else {
+ $scope.samlForceNameIdFormat = false;
+ }
+ }
+ if ($scope.client.attributes["saml.multivalued.roles"]) {
+ if ($scope.client.attributes["saml.multivalued.roles"] == "true") {
+ $scope.samlMultiValuedRoles = true;
+ } else {
+ $scope.samlMultiValuedRoles = false;
+ }
+ }
+ if ($scope.client.attributes["saml.force.post.binding"]) {
+ if ($scope.client.attributes["saml.force.post.binding"] == "true") {
+ $scope.samlForcePostBinding = true;
+ } else {
+ $scope.samlForcePostBinding = false;
+ }
+ }
}
if (!$scope.create) {
$scope.client = angular.copy(client);
updateProperties();
} else {
- $scope.client = { enabled: true, attributes: {}};
+ $scope.client = {
+ enabled: true,
+ standardFlowEnabled: true,
+ directAccessGrantsEnabled: true,
+ attributes: {}
+ };
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
$scope.client.redirectUris = [];
$scope.accessType = $scope.accessTypes[0];
@@ -816,63 +878,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.samlForceNameIdFormat = false;
}
- if ($scope.client.attributes["saml.server.signature"]) {
- if ($scope.client.attributes["saml.server.signature"] == "true") {
- $scope.samlServerSignature = true;
- } else {
- $scope.samlServerSignature = false;
-
- }
- }
- if ($scope.client.attributes["saml.assertion.signature"]) {
- if ($scope.client.attributes["saml.assertion.signature"] == "true") {
- $scope.samlAssertionSignature = true;
- } else {
- $scope.samlAssertionSignature = false;
- }
- }
- if ($scope.client.attributes["saml.client.signature"]) {
- if ($scope.client.attributes["saml.client.signature"] == "true") {
- $scope.samlClientSignature = true;
- } else {
- $scope.samlClientSignature = false;
- }
- }
- if ($scope.client.attributes["saml.encrypt"]) {
- if ($scope.client.attributes["saml.encrypt"] == "true") {
- $scope.samlEncrypt = true;
- } else {
- $scope.samlEncrypt = false;
- }
- }
- if ($scope.client.attributes["saml.authnstatement"]) {
- if ($scope.client.attributes["saml.authnstatement"] == "true") {
- $scope.samlAuthnStatement = true;
- } else {
- $scope.samlAuthnStatement = false;
- }
- }
- if ($scope.client.attributes["saml_force_name_id_format"]) {
- if ($scope.client.attributes["saml_force_name_id_format"] == "true") {
- $scope.samlForceNameIdFormat = true;
- } else {
- $scope.samlForceNameIdFormat = false;
- }
- }
- if ($scope.client.attributes["saml.multivalued.roles"]) {
- if ($scope.client.attributes["saml.multivalued.roles"] == "true") {
- $scope.samlMultiValuedRoles = true;
- } else {
- $scope.samlMultiValuedRoles = false;
- }
- }
- if ($scope.client.attributes["saml.force.post.binding"]) {
- if ($scope.client.attributes["saml.force.post.binding"] == "true") {
- $scope.samlForcePostBinding = true;
- } else {
- $scope.samlForcePostBinding = false;
- }
- }
$scope.importFile = function(fileContent){
console.debug(fileContent);
@@ -1039,7 +1044,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.client.attributes['saml.signature.algorithm'] = $scope.signatureAlgorithm;
$scope.client.attributes['saml_name_id_format'] = $scope.nameIdFormat;
- if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && !$scope.client.directGrantsOnly && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
+ if ($scope.client.protocol != 'saml' && !$scope.client.bearerOnly && ($scope.client.standardFlowEnabled || $scope.client.implicitFlowEnabled) && (!$scope.client.redirectUris || $scope.client.redirectUris.length == 0)) {
Notifications.error("You must specify at least one redirect uri");
} else {
if ($scope.create) {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index b2c7848..a66e205 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -899,6 +899,12 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.realm.accessTokenLifespan = TimeUnit.convert($scope.realm.accessTokenLifespan, from, to);
});
+ $scope.realm.accessTokenLifespanForImplicitFlowUnit = TimeUnit.autoUnit(realm.accessTokenLifespanForImplicitFlow);
+ $scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.toUnit(realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit);
+ $scope.$watch('realm.accessTokenLifespanForImplicitFlowUnit', function(to, from) {
+ $scope.realm.accessTokenLifespanForImplicitFlow = TimeUnit.convert($scope.realm.accessTokenLifespanForImplicitFlow, from, to);
+ });
+
$scope.realm.ssoSessionIdleTimeoutUnit = TimeUnit.autoUnit(realm.ssoSessionIdleTimeout);
$scope.realm.ssoSessionIdleTimeout = TimeUnit.toUnit(realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit);
$scope.$watch('realm.ssoSessionIdleTimeoutUnit', function(to, from) {
@@ -947,6 +953,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
$scope.save = function() {
var realmCopy = angular.copy($scope.realm);
delete realmCopy["accessTokenLifespanUnit"];
+ delete realmCopy["accessTokenLifespanForImplicitFlowUnit"];
delete realmCopy["ssoSessionMaxLifespanUnit"];
delete realmCopy["offlineSessionIdleTimeoutUnit"];
delete realmCopy["accessCodeLifespanUnit"];
@@ -955,6 +962,7 @@ module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http,
delete realmCopy["accessCodeLifespanLoginUnit"];
realmCopy.accessTokenLifespan = TimeUnit.toSeconds($scope.realm.accessTokenLifespan, $scope.realm.accessTokenLifespanUnit)
+ realmCopy.accessTokenLifespanForImplicitFlow = TimeUnit.toSeconds($scope.realm.accessTokenLifespanForImplicitFlow, $scope.realm.accessTokenLifespanForImplicitFlowUnit)
realmCopy.ssoSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.ssoSessionIdleTimeout, $scope.realm.ssoSessionIdleTimeoutUnit)
realmCopy.ssoSessionMaxLifespan = TimeUnit.toSeconds($scope.realm.ssoSessionMaxLifespan, $scope.realm.ssoSessionMaxLifespanUnit)
realmCopy.offlineSessionIdleTimeout = TimeUnit.toSeconds($scope.realm.offlineSessionIdleTimeout, $scope.realm.offlineSessionIdleTimeoutUnit)
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
index 443cbe9..eb54741 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html
@@ -59,13 +59,6 @@
</div>
<kc-tooltip>{{:: 'consent-required.tooltip' | translate}}</kc-tooltip>
</div>
- <div class="form-group clearfix block">
- <label class="col-md-2 control-label" for="directGrantsOnly">{{:: 'direct-grants-only' | translate}}</label>
- <div class="col-sm-6">
- <input ng-model="client.directGrantsOnly" name="directGrantsOnly" id="directGrantsOnly" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
- </div>
- <kc-tooltip>{{:: 'direct-grants-only.tooltip' | translate}}</kc-tooltip>
- </div>
<div class="form-group">
<label class="col-md-2 control-label" for="protocol">{{:: 'client-protocol' | translate}}</label>
<div class="col-sm-6">
@@ -92,6 +85,27 @@
</div>
<kc-tooltip>{{:: 'access-type.tooltip' | translate}}</kc-tooltip>
</div>
+ <div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
+ <label class="col-md-2 control-label" for="standardFlowEnabled">{{:: 'standard-flow-enabled' | translate}}</label>
+ <kc-tooltip>{{:: 'standard-flow-enabled.tooltip' | translate}}</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="client.standardFlowEnabled" name="standardFlowEnabled" id="standardFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+ </div>
+ </div>
+ <div class="form-group" data-ng-show="protocol == 'openid-connect' && client.publicClient && !client.bearerOnly">
+ <label class="col-md-2 control-label" for="implicitFlowEnabled">{{:: 'implicit-flow-enabled' | translate}}</label>
+ <kc-tooltip>{{:: 'implicit-flow-enabled.tooltip' | translate}}</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="client.implicitFlowEnabled" name="implicitFlowEnabled" id="implicitFlowEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+ </div>
+ </div>
+ <div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
+ <label class="col-md-2 control-label" for="directAccessGrantsEnabled">{{:: 'direct-access-grants-enabled' | translate}}</label>
+ <kc-tooltip>{{:: 'direct-access-grants-enabled.tooltip' | translate}}</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="client.directAccessGrantsEnabled" name="directAccessGrantsEnabled" id="directAccessGrantsEnabled" onoffswitch on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
+ </div>
+ </div>
<div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.publicClient && !client.bearerOnly">
<label class="col-md-2 control-label" for="serviceAccountsEnabled">{{:: 'service-accounts-enabled' | translate}}</label>
<kc-tooltip>{{:: 'service-accounts-enabled.tooltip' | translate}}</kc-tooltip>
@@ -202,7 +216,7 @@
<kc-tooltip>{{:: 'root-url.tooltip' | translate}}</kc-tooltip>
</div>
- <div class="form-group clearfix block" data-ng-hide="client.bearerOnly || client.directGrantsOnly">
+ <div class="form-group clearfix block" data-ng-hide="client.bearerOnly || (!client.standardFlowEnabled && !client.implicitFlowEnabled)">
<label class="col-md-2 control-label" for="newRedirectUri"><span class="required" data-ng-show="protocol != 'saml'">*</span> {{:: 'valid-redirect-uris' | translate}}</label>
<div class="col-sm-6">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/role-selector.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/role-selector.html
index 43b165b..531c3b2 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/role-selector.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/modal/role-selector.html
@@ -15,7 +15,7 @@
ng-options="r.name for r in realmRoles | orderBy:'toString()'">
<option style="display:none" value="">Select a role</option>
</select>
- <button class="btn btn-default" type="submit" ng-click="selectRealmRole()" tooltip-trigger="mouseover mouseout" tooltip="Select realm role" tooltip-placement="right">
+ <button class="btn btn-default" type="submit" data-ng-disabled="!selectedRealmRole.role" ng-click="selectRealmRole()" tooltip-trigger="mouseover mouseout" tooltip="Select realm role" tooltip-placement="right">
Select Realm Role</i>
</button>
</div>
@@ -32,7 +32,7 @@
ng-options="r.name for r in clientRoles | orderBy:'toString()'">
<option style="display:none" value="">Select a role</option>
</select>
- <button class="btn btn-default" type="submit" ng-click="selectClientRole()" tooltip-trigger="mouseover mouseout" tooltip="Select client role" tooltip-placement="right">
+ <button class="btn btn-default" type="submit" data-ng-disabled="!selectedClientRole.role" ng-click="selectClientRole()" tooltip-trigger="mouseover mouseout" tooltip="Select client role" tooltip-placement="right">
Select Client Role
</button>
</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
index 4e4224a..afc9a5f 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/protocol-mapper-detail.html
@@ -31,7 +31,7 @@
<div class="form-group clearfix">
<label class="col-md-2 control-label" for="name">{{:: 'name' | translate}}</label>
<div class="col-md-6">
- <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create">
+ <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create" required>
</div>
<kc-tooltip>{{:: 'mapper.name.tooltip' | translate}}</kc-tooltip>
</div>
@@ -56,7 +56,8 @@
<div>
<select class="form-control" id="mapperTypeCreate"
ng-model="mapperType"
- ng-options="mapperType.name for mapperType in mapperTypes">
+ ng-options="mapperType.name for mapperType in mapperTypes"
+ required>
</select>
</div>
</div>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
index dee13f6..bdfd390 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html
@@ -83,6 +83,23 @@
</div>
<div class="form-group">
+ <label class="col-md-2 control-label" for="accessTokenLifespanForImplicitFlow">{{:: 'access-token-lifespan-for-implicit-flow' | translate}}</label>
+
+ <div class="col-md-6 time-selector">
+ <input class="form-control" type="number" required min="1"
+ max="31536000" data-ng-model="realm.accessTokenLifespanForImplicitFlow"
+ id="accessTokenLifespanForImplicitFlow" name="accessTokenLifespanForImplicitFlow"/>
+ <select class="form-control" name="accessTokenLifespanForImplicitFlowUnit" data-ng-model="realm.accessTokenLifespanForImplicitFlowUnit">
+ <option data-ng-selected="!realm.accessTokenLifespanForImplicitFlowUnit" value="Seconds">{{:: 'seconds' | translate}}</option>
+ <option value="Minutes">{{:: 'minutes' | translate}}</option>
+ <option value="Hours">{{:: 'hours' | translate}}</option>
+ <option value="Days">{{:: 'days' | translate}}</option>
+ </select>
+ </div>
+ <kc-tooltip>{{:: 'access-token-lifespan-for-implicit-flow.tooltip' | translate}}</kc-tooltip>
+ </div>
+
+ <div class="form-group">
<label class="col-md-2 control-label" for="accessCodeLifespan">{{:: 'client-login-timeout' | translate}}</label>
<div class="col-md-6 time-selector">
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
index 6885308..ffe8328 100644
--- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -176,7 +176,8 @@ failedLogout=Logout failed
unknownLoginRequesterMessage=Unknown login requester
loginRequesterNotEnabledMessage=Login requester not enabled
bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
-directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
+standardFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Standard flow is disabled for the client.
+implicitFlowDisabledMessage=Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.
invalidRedirectUriMessage=Invalid redirect uri
unsupportedNameIdFormatMessage=Unsupported NameIDFormat
invlidRequesterMessage=Invalid requester
diff --git a/forms/common-themes/src/main/resources/theme/keycloak/login/resources/css/login.css b/forms/common-themes/src/main/resources/theme/keycloak/login/resources/css/login.css
index 55939b6..8f09a97 100644
--- a/forms/common-themes/src/main/resources/theme/keycloak/login/resources/css/login.css
+++ b/forms/common-themes/src/main/resources/theme/keycloak/login/resources/css/login.css
@@ -5,6 +5,7 @@
.kc-dropdown{
position: relative;
+ z-index: 9999;
}
.kc-dropdown > a{
position: absolute;
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
index b1bd124..b385592 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/CookieTokenStore.java
@@ -9,6 +9,7 @@ import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -58,10 +59,10 @@ public class CookieTokenStore {
AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) {
- JWSInput input = new JWSInput(idTokenString);
try {
+ JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new VerificationException(e);
}
} else {
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
index f41f80f..cc6b188 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java
@@ -11,6 +11,7 @@ import org.keycloak.common.VerificationException;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -313,10 +314,10 @@ public class OAuthRequestAuthenticator {
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) {
- JWSInput input = new JWSInput(idTokenString);
try {
+ JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new VerificationException();
}
}
diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
index 2588c40..a332f83 100755
--- a/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
+++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/PreAuthActionsHandler.java
@@ -3,6 +3,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
@@ -178,18 +179,17 @@ public class PreAuthActionsHandler {
return null;
}
- JWSInput input = new JWSInput(token);
- boolean verified = false;
try {
- verified = RSAProvider.verify(input, deployment.getRealmKey());
- } catch (Exception ignore) {
- }
- if (!verified) {
- log.warn("admin request failed, unable to verify token");
- facade.getResponse().sendError(403, "no token");
- return null;
+ JWSInput input = new JWSInput(token);
+ if (RSAProvider.verify(input, deployment.getRealmKey())) {
+ return input;
+ }
+ } catch (JWSInputException ignore) {
}
- return input;
+
+ log.warn("admin request failed, unable to verify token");
+ facade.getResponse().sendError(403, "no token");
+ return null;
}
diff --git a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
index c468f8d..652860b 100644
--- a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
+++ b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java
@@ -8,6 +8,7 @@ import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
@@ -195,10 +196,10 @@ public class KeycloakInstalled {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) {
- JWSInput input = new JWSInput(idTokenString);
try {
+ JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new VerificationException();
}
}
integration/js/src/main/resources/keycloak.js 261(+198 -63)
diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js
index 09e0d86..a74e1cc 100755
--- a/integration/js/src/main/resources/keycloak.js
+++ b/integration/js/src/main/resources/keycloak.js
@@ -36,6 +36,39 @@
if (initOptions.onLoad === 'login-required') {
kc.loginRequired = true;
}
+
+ if (initOptions.responseMode) {
+ if (initOptions.responseMode === 'query' || initOptions.responseMode === 'fragment') {
+ kc.responseMode = initOptions.responseMode;
+ } else {
+ throw 'Invalid value for responseMode';
+ }
+ }
+
+ if (initOptions.flow) {
+ switch (initOptions.flow) {
+ case 'standard':
+ kc.responseType = 'code';
+ break;
+ case 'implicit':
+ kc.responseType = 'id_token token';
+ break;
+ case 'hybrid':
+ kc.responseType = 'code id_token token';
+ break;
+ default:
+ throw 'Invalid value for flow';
+ }
+ kc.flow = initOptions.flow;
+ }
+ }
+
+ if (!kc.responseMode) {
+ kc.responseMode = 'fragment';
+ }
+ if (!kc.responseType) {
+ kc.responseType = 'code';
+ kc.flow = 'standard';
}
var promise = createPromise();
@@ -95,7 +128,7 @@
return;
} else if (initOptions) {
if (initOptions.token || initOptions.refreshToken) {
- setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
+ setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
if (loginIframe.enable) {
setupCheckLoginIframe().success(function() {
@@ -132,13 +165,14 @@
kc.createLoginUrl = function(options) {
var state = createUUID();
+ var nonce = createUUID();
var redirectUri = adapter.redirectUri(options);
if (options && options.prompt) {
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
- sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) });
+ sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@@ -150,7 +184,9 @@
+ '?client_id=' + encodeURIComponent(kc.clientId)
+ '&redirect_uri=' + encodeURIComponent(redirectUri)
+ '&state=' + encodeURIComponent(state)
- + '&response_type=code';
+ + '&nonce=' + encodeURIComponent(nonce)
+ + '&response_mode=' + encodeURIComponent(kc.responseMode)
+ + '&response_type=' + encodeURIComponent(kc.responseType);
if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
@@ -277,7 +313,7 @@
}
kc.isTokenExpired = function(minValidity) {
- if (!kc.tokenParsed || !kc.refreshToken) {
+ if (!kc.tokenParsed || (!kc.refreshToken && kc.flow != 'implicit' )) {
throw 'Not authenticated';
}
@@ -327,7 +363,7 @@
timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
+ setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
@@ -365,7 +401,7 @@
kc.clearToken = function() {
if (kc.token) {
- setToken(null, null, null);
+ setToken(null, null, null, true);
kc.onAuthLogout && kc.onAuthLogout();
if (kc.loginRequired) {
kc.login();
@@ -394,7 +430,21 @@
var error = oauth.error;
var prompt = oauth.prompt;
- if (code) {
+ var timeLocal = new Date().getTime();
+
+ if (error) {
+ if (prompt != 'none') {
+ kc.onAuthError && kc.onAuthError();
+ promise && promise.setError();
+ } else {
+ promise && promise.setSuccess();
+ }
+ return;
+ } else if ((kc.flow != 'standard') && (oauth.access_token || oauth.id_token)) {
+ authSuccess(oauth.access_token, null, oauth.id_token, true);
+ }
+
+ if ((kc.flow != 'implicit') && code) {
var params = 'code=' + code + '&grant_type=authorization_code';
var url = getRealmUrl() + '/protocol/openid-connect/token';
@@ -412,20 +462,12 @@
req.withCredentials = true;
- var timeLocal = new Date().getTime();
-
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.status == 200) {
- timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText);
- setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']);
-
- kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
-
- kc.onAuthSuccess && kc.onAuthSuccess();
- promise && promise.setSuccess();
+ authSuccess(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], kc.flow === 'standard');
} else {
kc.onAuthError && kc.onAuthError();
promise && promise.setError();
@@ -434,14 +476,30 @@
};
req.send(params);
- } else if (error) {
- if (prompt != 'none') {
- kc.onAuthError && kc.onAuthError();
+ }
+
+ function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
+ timeLocal = (timeLocal + new Date().getTime()) / 2;
+
+ setToken(accessToken, refreshToken, idToken, true);
+
+ if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
+ (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
+ (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
+
+ console.log('invalid nonce!');
+ kc.clearToken();
promise && promise.setError();
} else {
- promise && promise.setSuccess();
+ kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
+
+ if (fulfillPromise) {
+ kc.onAuthSuccess && kc.onAuthSuccess();
+ promise && promise.setSuccess();
+ }
}
}
+
}
function loadConfig(url) {
@@ -507,7 +565,12 @@
return promise.promise;
}
- function setToken(token, refreshToken, idToken) {
+ function setToken(token, refreshToken, idToken, useTokenTime) {
+ if (kc.tokenTimeoutHandle) {
+ clearTimeout(kc.tokenTimeoutHandle);
+ kc.tokenTimeoutHandle = null;
+ }
+
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
@@ -520,6 +583,13 @@
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
+
+ if (kc.onTokenExpired) {
+ var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
+ var expiresIn = kc.tokenParsed.exp - start;
+ kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
+ }
+
} else {
delete kc.token;
delete kc.tokenParsed;
@@ -597,53 +667,21 @@
}
function parseCallback(url) {
- if (url.indexOf('?') != -1) {
- var oauth = {};
+ var oauth = new CallbackParser(url, kc.responseMode).parseUri();
- oauth.newUrl = url.split('?')[0];
- var paramString = url.split('?')[1];
- var fragIndex = paramString.indexOf('#');
- if (fragIndex != -1) {
- paramString = paramString.substring(0, fragIndex);
- }
- var params = paramString.split('&');
- for (var i = 0; i < params.length; i++) {
- var p = params[i].split('=');
- switch (decodeURIComponent(p[0])) {
- case 'code':
- oauth.code = p[1];
- break;
- case 'error':
- oauth.error = p[1];
- break;
- case 'state':
- oauth.state = decodeURIComponent(p[1]);
- break;
- case 'redirect_fragment':
- oauth.fragment = decodeURIComponent(p[1]);
- break;
- case 'prompt':
- oauth.prompt = p[1];
- break;
- default:
- oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1];
- break;
- }
- }
-
- var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
+ var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
- if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) {
- delete sessionStorage.oauthState;
+ if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
+ delete sessionStorage.oauthState;
- oauth.redirectUri = sessionState.redirectUri;
+ oauth.redirectUri = sessionState.redirectUri;
+ oauth.storedNonce = sessionState.nonce;
- if (oauth.fragment) {
- oauth.newUrl += '#' + oauth.fragment;
- }
-
- return oauth;
+ if (oauth.fragment) {
+ oauth.newUrl += '#' + oauth.fragment;
}
+
+ return oauth;
}
}
@@ -907,6 +945,103 @@
throw 'invalid adapter type: ' + type;
}
+
+
+ var CallbackParser = function(uriToParse, responseMode) {
+ if (!(this instanceof CallbackParser)) {
+ return new CallbackParser(uriToParse, responseMode);
+ }
+ var parser = this;
+
+ var initialParse = function() {
+ var baseUri = null;
+ var queryString = null;
+ var fragmentString = null;
+
+ var questionMarkIndex = uriToParse.indexOf("?");
+ var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
+ if (questionMarkIndex == -1 && fragmentIndex == -1) {
+ baseUri = uriToParse;
+ } else if (questionMarkIndex != -1) {
+ baseUri = uriToParse.substring(0, questionMarkIndex);
+ queryString = uriToParse.substring(questionMarkIndex + 1);
+ if (fragmentIndex != -1) {
+ fragmentIndex = queryString.indexOf("#");
+ fragmentString = queryString.substring(fragmentIndex + 1);
+ queryString = queryString.substring(0, fragmentIndex);
+ }
+ } else {
+ baseUri = uriToParse.substring(0, fragmentIndex);
+ fragmentString = uriToParse.substring(fragmentIndex + 1);
+ }
+
+ return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
+ }
+
+ var parseParams = function(paramString) {
+ var result = {};
+ var params = paramString.split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ var paramName = decodeURIComponent(p[0]);
+ var paramValue = decodeURIComponent(p[1]);
+ result[paramName] = paramValue;
+ }
+ return result;
+ }
+
+ var handleQueryParam = function(paramName, paramValue, oauth) {
+ var supportedOAuthParams = [ 'code', 'error', 'state' ];
+
+ for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
+ if (paramName === supportedOAuthParams[i]) {
+ oauth[paramName] = paramValue;
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ parser.parseUri = function() {
+ var parsedUri = initialParse();
+
+ var queryParams = {};
+ if (parsedUri.queryString) {
+ queryParams = parseParams(parsedUri.queryString);
+ }
+
+ var oauth = { newUrl: parsedUri.baseUri };
+ for (var param in queryParams) {
+ switch (param) {
+ case 'redirect_fragment':
+ oauth.fragment = queryParams[param];
+ break;
+ case 'prompt':
+ oauth.prompt = queryParams[param];
+ break;
+ default:
+ if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
+ oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
+ }
+ break;
+ }
+ }
+
+ if (responseMode === 'fragment') {
+ var fragmentParams = {};
+ if (parsedUri.fragmentString) {
+ fragmentParams = parseParams(parsedUri.fragmentString);
+ }
+ for (var param in fragmentParams) {
+ oauth[param] = fragmentParams[param];
+ }
+ }
+
+ return oauth;
+ }
+ }
+
}
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
index 9c54ab1..be92dea 100755
--- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
+++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java
@@ -9,6 +9,7 @@ import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
@@ -153,10 +154,10 @@ public class ServletOAuthClient extends KeycloakDeploymentDelegateOAuthClient {
public static IDToken extractIdToken(String idToken) {
if (idToken == null) return null;
- JWSInput input = new JWSInput(idToken);
try {
+ JWSInput input = new JWSInput(idToken);
return input.readJsonContent(IDToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new RuntimeException(e);
}
}
integration/wildfly/pom.xml 2(+1 -1)
diff --git a/integration/wildfly/pom.xml b/integration/wildfly/pom.xml
index 3afdcc6..74ee90a 100644
--- a/integration/wildfly/pom.xml
+++ b/integration/wildfly/pom.xml
@@ -16,6 +16,6 @@
<modules>
<module>wildfly-adapter</module>
<module>wf8-subsystem</module>
- <module>wf9-subsystem</module>
+ <module>wildfly-subsystem</module>
</modules>
</project>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index e5c9484..06df073 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -5,6 +5,7 @@ import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0;
import org.keycloak.migration.migrators.MigrateTo1_6_0;
+import org.keycloak.migration.migrators.MigrateTo1_7_0;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.models.KeycloakSession;
@@ -54,6 +55,12 @@ public class MigrationModelManager {
}
new MigrateTo1_6_0().migrate(session);
}
+ if (stored == null || stored.lessThan(MigrateTo1_7_0.VERSION)) {
+ if (stored != null) {
+ logger.debug("Migrating older model to 1.7.0 updates");
+ }
+ new MigrateTo1_7_0().migrate(session);
+ }
model.setStoredVersion(MigrationModel.LATEST_VERSION);
}
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
new file mode 100644
index 0000000..e4ead4d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java
@@ -0,0 +1,23 @@
+package org.keycloak.migration.migrators;
+
+import java.util.List;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo1_7_0 {
+
+ public static final ModelVersion VERSION = new ModelVersion("1.7.0");
+
+ public void migrate(KeycloakSession session) {
+ List<RealmModel> realms = session.realms().getRealms();
+ for (RealmModel realm : realms) {
+ realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+ }
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java
index 051493e..492c2e1 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -111,12 +111,18 @@ public interface ClientModel extends RoleContainerModel {
boolean isPublicClient();
void setPublicClient(boolean flag);
- boolean isDirectGrantsOnly();
- void setDirectGrantsOnly(boolean flag);
-
boolean isConsentRequired();
void setConsentRequired(boolean consentRequired);
+ boolean isStandardFlowEnabled();
+ void setStandardFlowEnabled(boolean standardFlowEnabled);
+
+ boolean isImplicitFlowEnabled();
+ void setImplicitFlowEnabled(boolean implicitFlowEnabled);
+
+ boolean isDirectAccessGrantsEnabled();
+ void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled);
+
boolean isServiceAccountsEnabled();
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index 3ecc51c..c50e4cd 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -20,6 +20,8 @@ public interface Constants {
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
+ // 15 minutes
+ int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
// 30 days
int DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT = 2592000;
diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
index d04699f..b5edbaf 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java
@@ -30,6 +30,9 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private String baseUrl;
private boolean bearerOnly;
private boolean consentRequired;
+ private boolean standardFlowEnabled;
+ private boolean implicitFlowEnabled;
+ private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private boolean directGrantsOnly;
private int nodeReRegistrationTimeout;
@@ -243,6 +246,30 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.consentRequired = consentRequired;
}
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ this.standardFlowEnabled = standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ this.implicitFlowEnabled = implicitFlowEnabled;
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ this.directAccessGrantsEnabled = directAccessGrantsEnabled;
+ }
+
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 8f5c844..7a08bca 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -44,6 +44,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private int ssoSessionMaxLifespan;
private int offlineSessionIdleTimeout;
private int accessTokenLifespan;
+ private int accessTokenLifespanForImplicitFlow;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private int accessCodeLifespanLogin;
@@ -272,6 +273,14 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.accessTokenLifespan = accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
+ public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+ this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 62f9162..6c2c260 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -107,6 +107,9 @@ public interface RealmModel extends RoleContainerModel {
void setAccessTokenLifespan(int seconds);
+ int getAccessTokenLifespanForImplicitFlow();
+ void setAccessTokenLifespanForImplicitFlow(int seconds);
+
int getAccessCodeLifespan();
void setAccessCodeLifespan(int seconds);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
index af9b6b5..d3e4d4a 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/CredentialValidation.java
@@ -1,6 +1,7 @@
package org.keycloak.models.utils;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.PasswordPolicy;
@@ -73,11 +74,11 @@ public class CredentialValidation {
}
public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) {
- JWSInput jws = new JWSInput(encodedPasswordToken);
- if (!RSAProvider.verify(jws, realm.getPublicKey())) {
- return false;
- }
try {
+ JWSInput jws = new JWSInput(encodedPasswordToken);
+ if (!RSAProvider.verify(jws, realm.getPublicKey())) {
+ return false;
+ }
PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class);
if (!passwordToken.getRealm().equals(realm.getName())) {
return false;
@@ -89,7 +90,7 @@ public class CredentialValidation {
return false;
}
return true;
- } catch (IOException e) {
+ } catch (JWSInputException e) {
return false;
}
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
index 210f82b..1f42f57 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/HmacOTP.java
@@ -3,7 +3,7 @@ package org.keycloak.models.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
-import java.util.Random;
+import java.security.SecureRandom;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -29,7 +29,7 @@ public class HmacOTP {
public static String generateSecret(int length) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
- Random r = new Random();
+ SecureRandom r = new SecureRandom();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
char c = chars.charAt(r.nextInt(chars.length()));
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 723617b..e60d915 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -205,6 +205,7 @@ public class ModelToRepresentation {
rep.setEditUsernameAllowed(realm.isEditUsernameAllowed());
rep.setRevokeRefreshToken(realm.isRevokeRefreshToken());
rep.setAccessTokenLifespan(realm.getAccessTokenLifespan());
+ rep.setAccessTokenLifespanForImplicitFlow(realm.getAccessTokenLifespanForImplicitFlow());
rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout());
rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan());
rep.setOfflineSessionIdleTimeout(realm.getOfflineSessionIdleTimeout());
@@ -418,8 +419,10 @@ public class ModelToRepresentation {
rep.setFullScopeAllowed(clientModel.isFullScopeAllowed());
rep.setBearerOnly(clientModel.isBearerOnly());
rep.setConsentRequired(clientModel.isConsentRequired());
+ rep.setStandardFlowEnabled(clientModel.isStandardFlowEnabled());
+ rep.setImplicitFlowEnabled(clientModel.isImplicitFlowEnabled());
+ rep.setDirectAccessGrantsEnabled(clientModel.isDirectAccessGrantsEnabled());
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
- rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
rep.setRootUrl(clientModel.getRootUrl());
rep.setBaseUrl(clientModel.getBaseUrl());
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index dd82098..8360e48 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -105,6 +105,9 @@ public class RepresentationToModel {
if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
else newRealm.setAccessTokenLifespan(300);
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) newRealm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
+ else newRealm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
+
if (rep.getSsoSessionIdleTimeout() != null) newRealm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
else newRealm.setSsoSessionIdleTimeout(1800);
if (rep.getSsoSessionMaxLifespan() != null) newRealm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
@@ -603,6 +606,7 @@ public class RepresentationToModel {
if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore());
if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken());
if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan());
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) realm.setAccessTokenLifespanForImplicitFlow(rep.getAccessTokenLifespanForImplicitFlow());
if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan());
if (rep.getOfflineSessionIdleTimeout() != null) realm.setOfflineSessionIdleTimeout(rep.getOfflineSessionIdleTimeout());
@@ -772,8 +776,17 @@ public class RepresentationToModel {
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
+ if (resourceRep.isStandardFlowEnabled() != null) client.setStandardFlowEnabled(resourceRep.isStandardFlowEnabled());
+ if (resourceRep.isImplicitFlowEnabled() != null) client.setImplicitFlowEnabled(resourceRep.isImplicitFlowEnabled());
+ if (resourceRep.isDirectAccessGrantsEnabled() != null) client.setDirectAccessGrantsEnabled(resourceRep.isDirectAccessGrantsEnabled());
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
- if (resourceRep.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());
+
+ // Backwards compatibility only
+ if (resourceRep.isDirectGrantsOnly() != null) {
+ logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
+ client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
+ }
+
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
@@ -869,8 +882,10 @@ public class RepresentationToModel {
if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled());
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
+ if (rep.isStandardFlowEnabled() != null) resource.setStandardFlowEnabled(rep.isStandardFlowEnabled());
+ if (rep.isImplicitFlowEnabled() != null) resource.setImplicitFlowEnabled(rep.isImplicitFlowEnabled());
+ if (rep.isDirectAccessGrantsEnabled() != null) resource.setDirectAccessGrantsEnabled(rep.isDirectAccessGrantsEnabled());
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
- if (rep.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
if (rep.isFrontchannelLogout() != null) resource.setFrontchannelLogout(rep.isFrontchannelLogout());
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index e03452a..20c8725 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -163,16 +163,6 @@ public class ClientAdapter implements ClientModel {
}
- public boolean isDirectGrantsOnly() {
- if (updated != null) return updated.isDirectGrantsOnly();
- return cached.isDirectGrantsOnly();
- }
-
- public void setDirectGrantsOnly(boolean flag) {
- getDelegateForUpdate();
- updated.setDirectGrantsOnly(flag);
- }
-
public Set<RoleModel> getScopeMappings() {
if (updated != null) return updated.getScopeMappings();
Set<RoleModel> roles = new HashSet<RoleModel>();
@@ -452,6 +442,42 @@ public class ClientAdapter implements ClientModel {
}
@Override
+ public boolean isStandardFlowEnabled() {
+ if (updated != null) return updated.isStandardFlowEnabled();
+ return cached.isStandardFlowEnabled();
+ }
+
+ @Override
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ getDelegateForUpdate();
+ updated.setStandardFlowEnabled(standardFlowEnabled);
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ if (updated != null) return updated.isImplicitFlowEnabled();
+ return cached.isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ getDelegateForUpdate();
+ updated.setImplicitFlowEnabled(implicitFlowEnabled);
+ }
+
+ @Override
+ public boolean isDirectAccessGrantsEnabled() {
+ if (updated != null) return updated.isDirectAccessGrantsEnabled();
+ return cached.isDirectAccessGrantsEnabled();
+ }
+
+ @Override
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ getDelegateForUpdate();
+ updated.setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ }
+
+ @Override
public boolean isServiceAccountsEnabled() {
if (updated != null) return updated.isServiceAccountsEnabled();
return cached.isServiceAccountsEnabled();
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
index b046efc..87fe25b 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -1,11 +1,14 @@
package org.keycloak.models.cache.infinispan;
import org.infinispan.Cache;
+import org.infinispan.commands.write.RemoveCommand;
+import org.infinispan.container.entries.CacheEntry;
+import org.infinispan.context.InvocationContext;
+import org.infinispan.interceptors.base.CommandInterceptor;
import org.infinispan.notifications.Listener;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
-import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
-import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
-import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.infinispan.notifications.cachelistener.annotation.*;
+import org.infinispan.notifications.cachelistener.event.*;
+import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
@@ -21,6 +24,8 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class InfinispanCacheUserProviderFactory implements CacheUserProviderFactory {
+ private static final Logger log = Logger.getLogger(InfinispanCacheUserProviderFactory.class);
+
protected InfinispanUserCache userCache;
protected final RealmLookup usernameLookup = new RealmLookup();
@@ -82,36 +87,63 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
@CacheEntryCreated
public void userCreated(CacheEntryCreatedEvent<String, CachedUser> event) {
if (!event.isPre()) {
-
- CachedUser cachedUser;
+ CachedUser user;
// Try optimized version if available
if (isNewInfinispan) {
- cachedUser = event.getValue();
+ user = event.getValue();
} else {
String userId = event.getKey();
- cachedUser = event.getCache().get(userId);
+ user = event.getCache().get(userId);
}
- if (cachedUser != null) {
- String realm = cachedUser.getRealm();
- usernameLookup.put(realm, cachedUser.getUsername(), cachedUser.getId());
- if (cachedUser.getEmail() != null) {
- emailLookup.put(realm, cachedUser.getEmail(), cachedUser.getId());
+ if (user != null) {
+ String realm = user.getRealm();
+
+ usernameLookup.put(realm, user.getUsername(), user.getId());
+ if (user.getEmail() != null) {
+ emailLookup.put(realm, user.getEmail(), user.getId());
}
+
+ log.tracev("User added realm={0}, id={1}, username={2}", realm, user.getId(), user.getUsername());
}
}
}
@CacheEntryRemoved
public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
- if (event.isPre() && event.getValue() != null) {
- CachedUser cachedUser = event.getValue();
- String realm = cachedUser.getRealm();
- usernameLookup.remove(realm, cachedUser.getUsername());
- if (cachedUser.getEmail() != null) {
- emailLookup.remove(realm, cachedUser.getEmail());
- }
+ CachedUser user = event.getOldValue();
+ if (event.isPre() && user != null) {
+ removeUser(user);
+
+ log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
+ }
+ }
+
+ @CacheEntryInvalidated
+ public void userInvalidated(CacheEntryInvalidatedEvent<String, CachedUser> event) {
+ CachedUser user = event.getValue();
+ if (event.isPre() && user != null) {
+ removeUser(user);
+
+ log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void userEvicted(CacheEntriesEvictedEvent<String, CachedUser> event) {
+ for (CachedUser user : event.getEntries().values()) {
+ removeUser(user);
+
+ log.tracev("User evicted realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername());
+ }
+ }
+
+ private void removeUser(CachedUser cachedUser) {
+ String realm = cachedUser.getRealm();
+ usernameLookup.remove(realm, cachedUser.getUsername());
+ if (cachedUser.getEmail() != null) {
+ emailLookup.remove(realm, cachedUser.getEmail());
}
}
@@ -119,12 +151,12 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact
static class RealmLookup {
- protected final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> lookup = new ConcurrentHashMap<String, ConcurrentHashMap<String, String>>();
+ protected final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> lookup = new ConcurrentHashMap<>();
public void put(String realm, String key, String value) {
ConcurrentHashMap<String, String> map = lookup.get(realm);
if(map == null) {
- map = new ConcurrentHashMap<String, String>();
+ map = new ConcurrentHashMap<>();
ConcurrentHashMap<String, String> p = lookup.putIfAbsent(realm, map);
if (p != null) {
map = p;
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
index 02abe5e..c24f151 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java
@@ -301,6 +301,18 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ if (updated != null) return updated.getAccessTokenLifespanForImplicitFlow();
+ return cached.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ getDelegateForUpdate();
+ updated.setAccessTokenLifespanForImplicitFlow(seconds);
+ }
+
+ @Override
public int getAccessCodeLifespan() {
if (updated != null) return updated.getAccessCodeLifespan();
return cached.getAccessCodeLifespan();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
index 7c7b97d..a683956 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java
@@ -36,7 +36,6 @@ public class CachedClient implements Serializable {
private Map<String, String> attributes = new HashMap<String, String>();
private boolean publicClient;
private boolean fullScopeAllowed;
- private boolean directGrantsOnly;
private boolean frontchannelLogout;
private int notBefore;
private Set<String> scope = new HashSet<String>();
@@ -49,6 +48,9 @@ public class CachedClient implements Serializable {
private List<String> defaultRoles = new LinkedList<String>();
private boolean bearerOnly;
private boolean consentRequired;
+ private boolean standardFlowEnabled;
+ private boolean implicitFlowEnabled;
+ private boolean directAccessGrantsEnabled;
private boolean serviceAccountsEnabled;
private Map<String, String> roles = new HashMap<String, String>();
private int nodeReRegistrationTimeout;
@@ -67,7 +69,6 @@ public class CachedClient implements Serializable {
protocol = model.getProtocol();
attributes.putAll(model.getAttributes());
notBefore = model.getNotBefore();
- directGrantsOnly = model.isDirectGrantsOnly();
frontchannelLogout = model.isFrontchannelLogout();
publicClient = model.isPublicClient();
fullScopeAllowed = model.isFullScopeAllowed();
@@ -86,6 +87,9 @@ public class CachedClient implements Serializable {
defaultRoles.addAll(model.getDefaultRoles());
bearerOnly = model.isBearerOnly();
consentRequired = model.isConsentRequired();
+ standardFlowEnabled = model.isStandardFlowEnabled();
+ implicitFlowEnabled = model.isImplicitFlowEnabled();
+ directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
serviceAccountsEnabled = model.isServiceAccountsEnabled();
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
@@ -139,10 +143,6 @@ public class CachedClient implements Serializable {
return publicClient;
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnly;
- }
-
public int getNotBefore() {
return notBefore;
}
@@ -203,6 +203,18 @@ public class CachedClient implements Serializable {
return consentRequired;
}
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
public boolean isServiceAccountsEnabled() {
return serviceAccountsEnabled;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 4ae07fd..e423562 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -61,6 +61,7 @@ public class CachedRealm implements Serializable {
private int ssoSessionMaxLifespan;
private int offlineSessionIdleTimeout;
private int accessTokenLifespan;
+ private int accessTokenLifespanForImplicitFlow;
private int accessCodeLifespan;
private int accessCodeLifespanUserAction;
private int accessCodeLifespanLogin;
@@ -146,6 +147,7 @@ public class CachedRealm implements Serializable {
ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan();
offlineSessionIdleTimeout = model.getOfflineSessionIdleTimeout();
accessTokenLifespan = model.getAccessTokenLifespan();
+ accessTokenLifespanForImplicitFlow = model.getAccessTokenLifespanForImplicitFlow();
accessCodeLifespan = model.getAccessCodeLifespan();
accessCodeLifespanUserAction = model.getAccessCodeLifespanUserAction();
accessCodeLifespanLogin = model.getAccessCodeLifespanLogin();
@@ -347,6 +349,10 @@ public class CachedRealm implements Serializable {
return accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index a7dc0f4..f677859 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -3,6 +3,7 @@ package org.keycloak.models.jpa;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@@ -328,7 +329,7 @@ public class ClientAdapter implements ClientModel {
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
if (getProtocolMapperByName(model.getProtocol(), model.getName()) != null) {
- throw new RuntimeException("protocol mapper name must be unique per protocol");
+ throw new ModelDuplicateException("Protocol mapper name must be unique per protocol");
}
String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId();
ProtocolMapperEntity entity = new ProtocolMapperEntity();
@@ -498,23 +499,43 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public boolean isServiceAccountsEnabled() {
- return entity.isServiceAccountsEnabled();
+ public boolean isStandardFlowEnabled() {
+ return entity.isStandardFlowEnabled();
}
@Override
- public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
- entity.setServiceAccountsEnabled(serviceAccountsEnabled);
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ entity.setStandardFlowEnabled(standardFlowEnabled);
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ return entity.isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ entity.setImplicitFlowEnabled(implicitFlowEnabled);
}
@Override
- public boolean isDirectGrantsOnly() {
- return entity.isDirectGrantsOnly();
+ public boolean isDirectAccessGrantsEnabled() {
+ return entity.isDirectAccessGrantsEnabled();
}
@Override
- public void setDirectGrantsOnly(boolean flag) {
- entity.setDirectGrantsOnly(flag);
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ entity.setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ }
+
+ @Override
+ public boolean isServiceAccountsEnabled() {
+ return entity.isServiceAccountsEnabled();
+ }
+
+ @Override
+ public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
+ entity.setServiceAccountsEnabled(serviceAccountsEnabled);
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 6218e26..1569875 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -95,15 +95,21 @@ public class ClientEntity {
@Column(name="MANAGEMENT_URL")
private String managementUrl;
- @Column(name="DIRECT_GRANTS_ONLY")
- protected boolean directGrantsOnly;
-
@Column(name="BEARER_ONLY")
private boolean bearerOnly;
@Column(name="CONSENT_REQUIRED")
private boolean consentRequired;
+ @Column(name="STANDARD_FLOW_ENABLED")
+ private boolean standardFlowEnabled;
+
+ @Column(name="IMPLICIT_FLOW_ENABLED")
+ private boolean implicitFlowEnabled;
+
+ @Column(name="DIRECT_ACCESS_GRANTS_ENABLED")
+ private boolean directAccessGrantsEnabled;
+
@Column(name="SERVICE_ACCOUNTS_ENABLED")
private boolean serviceAccountsEnabled;
@@ -339,20 +345,36 @@ public class ClientEntity {
this.consentRequired = consentRequired;
}
- public boolean isServiceAccountsEnabled() {
- return serviceAccountsEnabled;
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabled;
}
- public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
- this.serviceAccountsEnabled = serviceAccountsEnabled;
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ this.standardFlowEnabled = standardFlowEnabled;
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabled;
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnly;
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ this.implicitFlowEnabled = implicitFlowEnabled;
}
- public void setDirectGrantsOnly(boolean directGrantsOnly) {
- this.directGrantsOnly = directGrantsOnly;
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabled;
+ }
+
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ this.directAccessGrantsEnabled = directAccessGrantsEnabled;
+ }
+
+ public boolean isServiceAccountsEnabled() {
+ return serviceAccountsEnabled;
+ }
+
+ public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
+ this.serviceAccountsEnabled = serviceAccountsEnabled;
}
public int getNodeReRegistrationTimeout() {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 6492b81..05af313 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -86,6 +86,8 @@ public class RealmEntity {
private int offlineSessionIdleTimeout;
@Column(name="ACCESS_TOKEN_LIFESPAN")
protected int accessTokenLifespan;
+ @Column(name="ACCESS_TOKEN_LIFE_IMPLICIT")
+ protected int accessTokenLifespanForImplicitFlow;
@Column(name="ACCESS_CODE_LIFESPAN")
protected int accessCodeLifespan;
@Column(name="USER_ACTION_LIFESPAN")
@@ -336,6 +338,14 @@ public class RealmEntity {
this.accessTokenLifespan = accessTokenLifespan;
}
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return accessTokenLifespanForImplicitFlow;
+ }
+
+ public void setAccessTokenLifespanForImplicitFlow(int accessTokenLifespanForImplicitFlow) {
+ this.accessTokenLifespanForImplicitFlow = accessTokenLifespanForImplicitFlow;
+ }
+
public int getAccessCodeLifespan() {
return accessCodeLifespan;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index acc5182..cb69f14 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -361,6 +361,16 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return realm.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ realm.setAccessTokenLifespanForImplicitFlow(seconds);
+ }
+
+ @Override
public int getSsoSessionIdleTimeout() {
return realm.getSsoSessionIdleTimeout();
}
@@ -715,6 +725,8 @@ public class RealmAdapter implements RealmModel {
entity.setId(id);
entity.setClientId(clientId);
entity.setEnabled(true);
+ entity.setStandardFlowEnabled(true);
+ entity.setDirectAccessGrantsEnabled(true);
entity.setRealm(realm);
realm.getClients().add(entity);
em.persist(entity);
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
index 8eb562f..eb6bcd9 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java
@@ -5,6 +5,7 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
@@ -342,7 +343,7 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
@Override
public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) {
if (getProtocolMapperByName(model.getProtocol(), model.getName()) != null) {
- throw new RuntimeException("protocol mapper name must be unique per protocol");
+ throw new ModelDuplicateException("Protocol mapper name must be unique per protocol");
}
ProtocolMapperEntity entity = new ProtocolMapperEntity();
String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId();
@@ -504,24 +505,46 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
}
@Override
- public boolean isServiceAccountsEnabled() {
- return getMongoEntity().isServiceAccountsEnabled();
+ public boolean isStandardFlowEnabled() {
+ return getMongoEntity().isStandardFlowEnabled();
}
@Override
- public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
- getMongoEntity().setServiceAccountsEnabled(serviceAccountsEnabled);
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ getMongoEntity().setStandardFlowEnabled(standardFlowEnabled);
+ updateMongoEntity();
+ }
+
+ @Override
+ public boolean isImplicitFlowEnabled() {
+ return getMongoEntity().isImplicitFlowEnabled();
+ }
+
+ @Override
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ getMongoEntity().setImplicitFlowEnabled(implicitFlowEnabled);
updateMongoEntity();
}
@Override
- public boolean isDirectGrantsOnly() {
- return getMongoEntity().isDirectGrantsOnly();
+ public boolean isDirectAccessGrantsEnabled() {
+ return getMongoEntity().isDirectAccessGrantsEnabled();
}
@Override
- public void setDirectGrantsOnly(boolean flag) {
- getMongoEntity().setDirectGrantsOnly(flag);
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ getMongoEntity().setDirectAccessGrantsEnabled(directAccessGrantsEnabled);
+ updateMongoEntity();
+ }
+
+ @Override
+ public boolean isServiceAccountsEnabled() {
+ return getMongoEntity().isServiceAccountsEnabled();
+ }
+
+ @Override
+ public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) {
+ getMongoEntity().setServiceAccountsEnabled(serviceAccountsEnabled);
updateMongoEntity();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index d31503c..c8fc1f0 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -369,6 +369,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public int getAccessTokenLifespanForImplicitFlow() {
+ return realm.getAccessTokenLifespanForImplicitFlow();
+ }
+
+ @Override
+ public void setAccessTokenLifespanForImplicitFlow(int seconds) {
+ realm.setAccessTokenLifespanForImplicitFlow(seconds);
+ updateRealm();
+ }
+
+ @Override
public int getAccessCodeLifespan() {
return realm.getAccessCodeLifespan();
}
@@ -799,6 +810,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
clientEntity.setClientId(clientId);
clientEntity.setRealmId(getId());
clientEntity.setEnabled(true);
+ clientEntity.setStandardFlowEnabled(true);
+ clientEntity.setDirectAccessGrantsEnabled(true);
getMongoStore().insertEntity(clientEntity, invocationContext);
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
pom.xml 12(+6 -6)
diff --git a/pom.xml b/pom.xml
index 62ae25c..7e20bc3 100755
--- a/pom.xml
+++ b/pom.xml
@@ -838,7 +838,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-subsystem</artifactId>
+ <artifactId>keycloak-wildfly-subsystem</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
@@ -1048,7 +1048,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-subsystem</artifactId>
+ <artifactId>keycloak-saml-wildfly-subsystem</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
@@ -1137,25 +1137,25 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-modules</artifactId>
+ <artifactId>keycloak-wildfly-modules</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-modules</artifactId>
+ <artifactId>keycloak-saml-wildfly-modules</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-saml-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
</dependency>
diff --git a/saml/client-adapter/as7-eap6/adapter/pom.xml b/saml/client-adapter/as7-eap6/adapter/pom.xml
index 6d1ace8..da632c7 100755
--- a/saml/client-adapter/as7-eap6/adapter/pom.xml
+++ b/saml/client-adapter/as7-eap6/adapter/pom.xml
@@ -31,10 +31,6 @@
<artifactId>keycloak-saml-adapter-core</artifactId>
</dependency>
<dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-adapter-core</artifactId>
- </dependency>
- <dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
diff --git a/saml/client-adapter/as7-eap6/subsystem/pom.xml b/saml/client-adapter/as7-eap6/subsystem/pom.xml
index fa87dcf..872e5d1 100755
--- a/saml/client-adapter/as7-eap6/subsystem/pom.xml
+++ b/saml/client-adapter/as7-eap6/subsystem/pom.xml
@@ -101,9 +101,9 @@ projects that depend on this project.-->
</dependency>
<dependency>
- <groupId>org.jboss.msc</groupId>
- <artifactId>jboss-msc</artifactId>
- <version>1.0.2.GA</version>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-controller</artifactId>
+ <version>${jboss.version}</version>
</dependency>
<dependency>
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java
new file mode 100644
index 0000000..966cd67
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class Configuration {
+
+ static final Configuration INSTANCE = new Configuration();
+
+ private ModelNode config = new ModelNode();
+
+ private Configuration() {
+ }
+
+ void updateModel(ModelNode operation, ModelNode model) {
+ ModelNode node = config;
+ ModelNode addr = operation.get("address");
+ for (Property item : addr.asPropertyList()) {
+ node = getNodeForAddressElement(node, item);
+ }
+ node.set(model);
+ }
+
+ private ModelNode getNodeForAddressElement(ModelNode node, Property item) {
+ String key = item.getValue().asString();
+ ModelNode keymodel = node.get(item.getName());
+ return keymodel.get(key);
+ }
+
+ public ModelNode getSecureDeployment(String name) {
+ ModelNode secureDeployment = config.get("subsystem").get("keycloak-saml").get(Constants.Model.SECURE_DEPLOYMENT);
+ if (secureDeployment.hasDefined(name)) {
+ return secureDeployment.get(name);
+ }
+ return null;
+ }
+
+ public boolean isSecureDeployment(String name) {
+ return getSecureDeployment(name) != null;
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
new file mode 100644
index 0000000..07af4f7
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class Constants {
+
+ static class Model {
+ static final String SECURE_DEPLOYMENT = "secure-deployment";
+ static final String SERVICE_PROVIDER = "service-provider";
+
+ static final String SSL_POLICY = "ssl-policy";
+ static final String NAME_ID_POLICY_FORMAT = "name-id-policy-format";
+ static final String LOGOUT_PAGE = "logout-page";
+ static final String FORCE_AUTHENTICATION = "force-authentication";
+ static final String ROLE_ATTRIBUTES = "role-attributes";
+ static final String SIGNING = "signing";
+ static final String ENCRYPTION = "encryption";
+ static final String KEY = "key";
+ static final String RESOURCE = "resource";
+ static final String PASSWORD = "password";
+
+ static final String PRIVATE_KEY_ALIAS = "private-key-alias";
+ static final String PRIVATE_KEY_PASSWORD = "private-key-password";
+ static final String CERTIFICATE_ALIAS = "certificate-alias";
+ static final String KEY_STORE = "key-store";
+ static final String SIGN_REQUEST = "sign-request";
+ static final String VALIDATE_RESPONSE_SIGNATURE = "validate-response-signature";
+ static final String REQUEST_BINDING = "request-binding";
+ static final String BINDING_URL = "binding-url";
+ static final String VALIDATE_REQUEST_SIGNATURE = "validate-request-signature";
+ static final String SIGN_RESPONSE = "sign-response";
+ static final String RESPONSE_BINDING = "response-binding";
+ static final String POST_BINDING_URL = "post-binding-url";
+ static final String REDIRECT_BINDING_URL = "redirect-binding-url";
+ static final String SINGLE_SIGN_ON = "single-sign-on";
+ static final String SINGLE_LOGOUT = "single-logout";
+ static final String IDENTITY_PROVIDER = "identity-provider";
+ static final String PRINCIPAL_NAME_MAPPING_POLICY = "principal-name-mapping-policy";
+ static final String PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME = "principal-name-mapping-attribute-name";
+ static final String SIGNATURE_ALGORITHM = "signature-algorithm";
+ static final String SIGNATURE_CANONICALIZATION_METHOD = "signature-canonicalization-method";
+ static final String PRIVATE_KEY_PEM = "private-key-pem";
+ static final String PUBLIC_KEY_PEM = "public-key-pem";
+ static final String CERTIFICATE_PEM = "certificate-pem";
+ static final String TYPE = "type";
+ static final String ALIAS = "alias";
+ static final String FILE = "file";
+ static final String SIGNATURES_REQUIRED = "signatures-required";
+ }
+
+
+ static class XML {
+ static final String SECURE_DEPLOYMENT = "secure-deployment";
+ static final String SERVICE_PROVIDER = "SP";
+
+ static final String NAME = "name";
+ static final String ENTITY_ID = "entityID";
+ static final String SSL_POLICY = "sslPolicy";
+ static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
+ static final String LOGOUT_PAGE = "logoutPage";
+ static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String ROLE_IDENTIFIERS = "RoleIdentifiers";
+ static final String SIGNING = "signing";
+ static final String ENCRYPTION = "encryption";
+ static final String KEYS = "Keys";
+ static final String KEY = "Key";
+ static final String RESOURCE = "resource";
+ static final String PASSWORD = "password";
+ static final String KEY_STORE = "KeyStore";
+ static final String PRIVATE_KEY = "PrivateKey";
+ static final String CERTIFICATE = "Certificate";
+
+ static final String PRIVATE_KEY_ALIAS = "alias";
+ static final String PRIVATE_KEY_PASSWORD = "password";
+ static final String CERTIFICATE_ALIAS = "alias";
+ static final String SIGN_REQUEST = "signRequest";
+ static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
+ static final String REQUEST_BINDING = "requestBinding";
+ static final String BINDING_URL = "bindingUrl";
+ static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
+ static final String SIGN_RESPONSE = "signResponse";
+ static final String RESPONSE_BINDING = "responseBinding";
+ static final String POST_BINDING_URL = "postBindingUrl";
+ static final String REDIRECT_BINDING_URL = "redirectBindingUrl";
+ static final String SINGLE_SIGN_ON = "SingleSignOnService";
+ static final String SINGLE_LOGOUT = "SingleLogoutService";
+ static final String IDENTITY_PROVIDER = "IDP";
+ static final String PRINCIPAL_NAME_MAPPING = "PrincipalNameMapping";
+ static final String PRINCIPAL_NAME_MAPPING_POLICY = "policy";
+ static final String PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME = "attribute";
+ static final String ATTRIBUTE = "Attribute";
+ static final String SIGNATURE_ALGORITHM = "signatureAlgorithm";
+ static final String SIGNATURE_CANONICALIZATION_METHOD = "signatureCanonicalizationMethod";
+ static final String PRIVATE_KEY_PEM = "PrivateKeyPem";
+ static final String PUBLIC_KEY_PEM = "PublicKeyPem";
+ static final String CERTIFICATE_PEM = "CertificatePem";
+ static final String TYPE = "type";
+ static final String ALIAS = "alias";
+ static final String FILE = "file";
+ static final String SIGNATURES_REQUIRED = "signaturesRequired";
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java
new file mode 100644
index 0000000..679658b
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+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;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class IdentityProviderAddHandler extends AbstractAddStepHandler {
+
+ IdentityProviderAddHandler() {
+ super(IdentityProviderDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java
new file mode 100644
index 0000000..09262f9
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ObjectTypeAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class IdentityProviderDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SIGNATURES_REQUIRED =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURES_REQUIRED, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGNATURES_REQUIRED)
+ .build();
+
+ static final SimpleAttributeDefinition SIGNATURE_ALGORITHM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURE_ALGORITHM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SIGNATURE_ALGORITHM)
+ .build();
+
+ static final SimpleAttributeDefinition SIGNATURE_CANONICALIZATION_METHOD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURE_CANONICALIZATION_METHOD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SIGNATURE_CANONICALIZATION_METHOD)
+ .build();
+
+ static final ObjectTypeAttributeDefinition SINGLE_SIGN_ON =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.SINGLE_SIGN_ON,
+ SingleSignOnDefinition.ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final ObjectTypeAttributeDefinition SINGLE_LOGOUT =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.SINGLE_LOGOUT,
+ SingleLogoutDefinition.ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD};
+
+ static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final IdentityProviderDefinition INSTANCE = new IdentityProviderDefinition();
+
+ private IdentityProviderDefinition() {
+ super(PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.IDENTITY_PROVIDER),
+ new IdentityProviderAddHandler(),
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
\ No newline at end of file
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java
new file mode 100644
index 0000000..b362d4f
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+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;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class KeyAddHandler extends AbstractAddStepHandler {
+
+ KeyAddHandler() {
+ super(KeyDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java
index 31008d4..68e4679 100755
--- a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java
@@ -21,13 +21,22 @@ 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.web.deployment.WarMetaData;
+import org.jboss.dmr.ModelNode;
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.jboss.staxmapper.XMLExtendedStreamWriter;
+import org.keycloak.adapters.saml.AdapterConstants;
import org.keycloak.adapters.saml.jbossweb.SamlAuthenticatorValve;
+import org.keycloak.subsystem.saml.as7.logging.KeycloakLogger;
+import org.keycloak.subsystem.saml.as7.xml.FormattingXMLStreamWriter;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@@ -39,22 +48,16 @@ import java.util.List;
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.saml.adapterConfig";
-
-
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
String deploymentName = deploymentUnit.getName();
- // if it's not a web-app there's nothing to secure
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) {
return;
}
+
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
webMetaData = new JBossWebMetaData();
@@ -64,12 +67,66 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
// otherwise
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
- boolean webRequiresKC = loginConfig != null && "KEYCLOAK-SAML".equalsIgnoreCase(loginConfig.getAuthMethod());
+ try {
+ boolean webRequiresKC = loginConfig != null && "KEYCLOAK-SAML".equalsIgnoreCase(loginConfig.getAuthMethod());
+ boolean hasSubsystemConfig = Configuration.INSTANCE.isSecureDeployment(deploymentName);
+ if (hasSubsystemConfig || webRequiresKC) {
+ log.debug("Setting up KEYCLOAK-SAML auth method for WAR: " + deploymentName);
+
+ // if secure-deployment configuration exists for web app, we force KEYCLOAK-SAML auth method on it
+ if (hasSubsystemConfig) {
+ addXMLData(getXML(deploymentName), warMetaData);
+ if (loginConfig != null) {
+ loginConfig.setAuthMethod("KEYCLOAK-SAML");
+ //loginConfig.setRealmName(service.getRealmName(deploymentName));
+ } else {
+ log.warn("Failed to set up KEYCLOAK-SAML auth method for WAR: " + deploymentName + " (loginConfig == null)");
+ }
+ }
+ addValve(webMetaData);
+ KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
+ }
+ } catch (Exception e) {
+ throw new DeploymentUnitProcessingException("Failed to configure KeycloakSamlExtension from subsystem model", e);
+ }
+ }
+
+ private String getXML(String deploymentName) throws XMLStreamException {
+ ModelNode node = Configuration.INSTANCE.getSecureDeployment(deploymentName);
+ if (node != null) {
+ KeycloakSubsystemParser writer = new KeycloakSubsystemParser();
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ XMLExtendedStreamWriter streamWriter = new FormattingXMLStreamWriter(XMLOutputFactory.newInstance().createXMLStreamWriter(output));
+ try {
+ streamWriter.writeStartElement("keycloak-saml-adapter");
+ writer.writeSps(streamWriter, node);
+ streamWriter.writeEndElement();
+ } finally {
+ streamWriter.close();
+ }
+ return new String(output.toByteArray(), Charset.forName("utf-8"));
+ }
+ return null;
+ }
+
+ private void addXMLData(String xml, WarMetaData warMetaData) {
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
- if (webRequiresKC) {
- log.debug("Setting up KEYCLOAK-SAML auth method for WAR: " + deploymentName);
- addValve(webMetaData);
+ List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<>();
}
+
+ ParamValueMetaData param = new ParamValueMetaData();
+ param.setParamName(AdapterConstants.AUTH_DATA_PARAM_NAME);
+ param.setParamValue(xml);
+ contextParams.add(param);
+
+ webMetaData.setContextParams(contextParams);
}
private void addValve(JBossWebMetaData webMetaData) {
@@ -89,5 +146,4 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
public void undeploy(DeploymentUnit du) {
}
-
}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java
index c52f2b5..d936982 100755
--- a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java
@@ -18,6 +18,7 @@ package org.keycloak.subsystem.saml.as7;
import org.jboss.as.controller.Extension;
import org.jboss.as.controller.ExtensionContext;
+import org.jboss.as.controller.ModelVersion;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.SubsystemRegistration;
@@ -36,15 +37,12 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUB
public class KeycloakSamlExtension implements Extension {
public static final String SUBSYSTEM_NAME = "keycloak-saml";
- public static final String NAMESPACE = "urn:jboss:domain:keycloak-saml:1.6";
+ public static final String NAMESPACE = "urn:jboss:domain:keycloak-saml:1.1";
private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
private static final String RESOURCE_NAME = KeycloakSamlExtension.class.getPackage().getName() + ".LocalDescriptions";
- private static final int MGMT_API_VERSION_MAJOR = 1;
- private static final int MGMT_API_VERSION_MINOR = 1;
-
+ private static final ModelVersion MGMT_API_VERSION = ModelVersion.create(1, 1, 0);
static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
- private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@@ -67,10 +65,15 @@ public class KeycloakSamlExtension implements Extension {
*/
@Override
public void initialize(final ExtensionContext context) {
- final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION_MAJOR, MGMT_API_VERSION_MINOR);
-
- ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
+ final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME,
+ MGMT_API_VERSION.getMajor(), MGMT_API_VERSION.getMinor(), MGMT_API_VERSION.getMicro());
+ ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KeycloakSubsystemDefinition.INSTANCE);
+ ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SecureDeploymentDefinition.INSTANCE);
+ ManagementResourceRegistration serviceProviderRegistration = secureDeploymentRegistration.registerSubModel(ServiceProviderDefinition.INSTANCE);
+ serviceProviderRegistration.registerSubModel(KeyDefinition.INSTANCE);
+ ManagementResourceRegistration idpRegistration = serviceProviderRegistration.registerSubModel(IdentityProviderDefinition.INSTANCE);
+ idpRegistration.registerSubModel(KeyDefinition.INSTANCE);
subsystem.registerXMLElementWriter(PARSER);
}
}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
index 2a7fd55..eda678f 100755
--- a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
@@ -16,13 +16,12 @@
*/
package org.keycloak.subsystem.saml.as7;
-
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.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.Phase;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
@@ -43,17 +42,20 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
context.addStep(new AbstractDeploymentChainStep() {
@Override
protected void execute(DeploymentProcessorTarget processorTarget) {
- processorTarget.addDeploymentProcessor(Phase.DEPENDENCIES, 0, new KeycloakDependencyProcessorAS7());
- processorTarget.addDeploymentProcessor(
+ processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, chooseDependencyProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakSamlExtension.SUBSYSTEM_NAME,
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
- new KeycloakAdapterConfigDeploymentProcessor());
+ chooseConfigDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
}
- @Override
- protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
- model.setEmptyObject();
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorAS7();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
}
}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java
index 400822e..4b7ef3f 100755
--- a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java
@@ -14,23 +14,23 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-
package org.keycloak.subsystem.saml.as7;
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;
/**
- * Definition of subsystem=keycloak.
+ * Definition of subsystem=keycloak-saml.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
- protected KeycloakSubsystemDefinition() {
+
+ static final KeycloakSubsystemDefinition INSTANCE = new KeycloakSubsystemDefinition();
+
+ private KeycloakSubsystemDefinition() {
super(KeycloakSamlExtension.SUBSYSTEM_PATH,
KeycloakSamlExtension.getResourceDescriptionResolver("subsystem"),
KeycloakSubsystemAdd.INSTANCE,
@@ -41,7 +41,6 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
- resourceRegistration.registerOperationHandler(ModelDescriptionConstants.DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
}
-
}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java
index 14899e1..0b2cef9 100755
--- a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * Copyright 2015 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
@@ -17,9 +17,14 @@
package org.keycloak.subsystem.saml.as7;
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.operations.common.Util;
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;
@@ -27,6 +32,10 @@ import org.jboss.staxmapper.XMLExtendedStreamWriter;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -41,10 +50,15 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> list) throws XMLStreamException {
// Require no attributes
ParseUtils.requireNoAttributes(reader);
- ModelNode addKeycloakSub = org.jboss.as.controller.operations.common.Util.createAddOperation(PathAddress.pathAddress(KeycloakSamlExtension.PATH_SUBSYSTEM));
+ ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakSamlExtension.PATH_SUBSYSTEM));
list.add(addKeycloakSub);
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ if (reader.getLocalName().equals(Constants.XML.SECURE_DEPLOYMENT)) {
+ readSecureDeployment(reader, list);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
}
}
@@ -53,6 +67,319 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
return reader.nextTag();
}
+ void readSecureDeployment(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
+ String name = readRequiredAttribute(reader, Constants.XML.NAME);
+
+ PathAddress addr = PathAddress.pathAddress(
+ PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakSamlExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT, name));
+ ModelNode addSecureDeployment = Util.createAddOperation(addr);
+ list.add(addSecureDeployment);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (tagName.equals(Constants.XML.SERVICE_PROVIDER)) {
+ readServiceProvider(reader, list, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readServiceProvider(XMLExtendedStreamReader reader, List<ModelNode> list, PathAddress parentAddr) throws XMLStreamException {
+ String entityId = readRequiredAttribute(reader, Constants.XML.ENTITY_ID);
+
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.SERVICE_PROVIDER, entityId));
+ ModelNode addServiceProvider = Util.createAddOperation(addr);
+ list.add(addServiceProvider);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ if (Constants.XML.ENTITY_ID.equals(name)) {
+ continue;
+ }
+
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = ServiceProviderDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addServiceProvider, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.KEYS.equals(tagName)) {
+ readKeys(list, reader, addr);
+ } else if (Constants.XML.PRINCIPAL_NAME_MAPPING.equals(tagName)) {
+ readPrincipalNameMapping(addServiceProvider, reader);
+ } else if (Constants.XML.ROLE_IDENTIFIERS.equals(tagName)) {
+ readRoleIdentifiers(addServiceProvider, reader);
+ } else if (Constants.XML.IDENTITY_PROVIDER.equals(tagName)) {
+ readIdentityProvider(list, reader, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readIdentityProvider(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ String entityId = readRequiredAttribute(reader, Constants.XML.ENTITY_ID);
+
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER, entityId));
+ ModelNode addIdentityProvider = Util.createAddOperation(addr);
+ list.add(addIdentityProvider);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ if (Constants.XML.ENTITY_ID.equals(name)
+ // don't break if encountering this noop attr from client-adapter/core keycloak_saml_adapter_1_6.xsd
+ || "encryption".equals(name)) {
+ continue;
+ }
+ SimpleAttributeDefinition attr = IdentityProviderDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addIdentityProvider, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.SINGLE_SIGN_ON.equals(tagName)) {
+ readSingleSignOn(addIdentityProvider, reader);
+ } else if (Constants.XML.SINGLE_LOGOUT.equals(tagName)) {
+ readSingleLogout(addIdentityProvider, reader);
+ } else if (Constants.XML.KEYS.equals(tagName)) {
+ readKeys(list, reader, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readSingleSignOn(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode sso = addIdentityProvider.get(Constants.Model.SINGLE_SIGN_ON);
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = SingleSignOnDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, sso, reader);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readSingleLogout(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode slo = addIdentityProvider.get(Constants.Model.SINGLE_LOGOUT);
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = SingleLogoutDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, slo, reader);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readKeys(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+ List<ModelNode> keyList = new LinkedList<>();
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (!Constants.XML.KEY.equals(tagName)) {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ readKey(keyList, reader, parentAddr);
+ }
+ list.addAll(keyList);
+ }
+
+ void readKey(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.KEY, "key-" + list.size()));
+ ModelNode addKey = Util.createAddOperation(addr);
+ list.add(addKey);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKey, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.KEY_STORE.equals(tagName)) {
+ readKeyStore(addKey, reader);
+ } else if (Constants.XML.PRIVATE_KEY_PEM.equals(tagName)
+ || Constants.XML.PUBLIC_KEY_PEM.equals(tagName)
+ || Constants.XML.CERTIFICATE_PEM.equals(tagName)) {
+
+ readNoAttrElementContent(KeyDefinition.lookupElement(tagName), addKey, reader);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readNoAttrElementContent(SimpleAttributeDefinition attr, ModelNode model, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+ String value = reader.getElementText();
+ attr.parseAndSetParameter(value, model, reader);
+ }
+
+ void readKeyStore(ModelNode addKey, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode addKeyStore = addKey.get(Constants.Model.KEY_STORE);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStoreDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.FILE) && !addKeyStore.hasDefined(Constants.Model.RESOURCE)) {
+ throw new XMLStreamException("KeyStore element must have 'file' or 'resource' attribute set", reader.getLocation());
+ }
+ if (!addKeyStore.hasDefined(Constants.Model.PASSWORD)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PASSWORD);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (Constants.XML.PRIVATE_KEY.equals(tagName)) {
+ readPrivateKey(reader, addKeyStore);
+ } else if (Constants.XML.CERTIFICATE.equals(tagName)) {
+ readCertificate(reader, addKeyStore);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+
+ void readPrivateKey(XMLExtendedStreamReader reader, ModelNode addKeyStore) throws XMLStreamException {
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStorePrivateKeyDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.PRIVATE_KEY_ALIAS)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRIVATE_KEY_ALIAS);
+ }
+ if (!addKeyStore.hasDefined(Constants.Model.PRIVATE_KEY_PASSWORD)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRIVATE_KEY_PASSWORD);
+ }
+
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readCertificate(XMLExtendedStreamReader reader, ModelNode addKeyStore) throws XMLStreamException {
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStoreCertificateDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.CERTIFICATE_ALIAS)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.CERTIFICATE_ALIAS);
+ }
+
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readRoleIdentifiers(ModelNode addServiceProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (!Constants.XML.ATTRIBUTE.equals(tagName)) {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+
+ ParseUtils.requireSingleAttribute(reader, Constants.XML.NAME);
+ String name = ParseUtils.readStringAttributeElement(reader, Constants.XML.NAME);
+
+ ServiceProviderDefinition.ROLE_ATTRIBUTES.parseAndAddParameterElement(name, addServiceProvider, reader);
+ }
+ }
+
+ void readPrincipalNameMapping(ModelNode addServiceProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+
+ boolean policySet = false;
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ if (Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY.equals(name)) {
+ policySet = true;
+ ServiceProviderDefinition.PRINCIPAL_NAME_MAPPING_POLICY.parseAndSetParameter(value, addServiceProvider, reader);
+ } else if (Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME.equals(name)) {
+ ServiceProviderDefinition.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME.parseAndSetParameter(value, addServiceProvider, reader);
+ } else {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ }
+
+ if (!policySet) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ /**
+ * Read an attribute, and throw exception if attribute is not present
+ */
+ String readRequiredAttribute(XMLExtendedStreamReader reader, String attrName) throws XMLStreamException {
+ String value = null;
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String attr = reader.getAttributeLocalName(i);
+ if (attr.equals(attrName)) {
+ value = reader.getAttributeValue(i);
+ break;
+ }
+ }
+ if (value == null) {
+ throw ParseUtils.missingRequired(reader, Collections.singleton(attrName));
+ }
+ return value;
+ }
/**
* {@inheritDoc}
@@ -60,8 +387,183 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
@Override
public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
context.startSubsystemElement(KeycloakSamlExtension.NAMESPACE, false);
+ writeSecureDeployment(writer, context.getModelNode());
+ writer.writeEndElement();
+ }
+
+ public void writeSecureDeployment(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.get(Constants.Model.SECURE_DEPLOYMENT).isDefined()) {
+ return;
+ }
+
+ for (Property sp : model.get(Constants.Model.SECURE_DEPLOYMENT).asPropertyList()) {
+ writer.writeStartElement(Constants.XML.SECURE_DEPLOYMENT);
+ writer.writeAttribute(Constants.XML.NAME, sp.getName());
+
+ writeSps(writer, sp.getValue());
+ writer.writeEndElement();
+ }
+ }
+
+ void writeSps(final XMLExtendedStreamWriter writer, final ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ for (Property sp : model.get(Constants.Model.SERVICE_PROVIDER).asPropertyList()) {
+ writer.writeStartElement(Constants.XML.SERVICE_PROVIDER);
+ writer.writeAttribute(Constants.XML.ENTITY_ID, sp.getName());
+ ModelNode spAttributes = sp.getValue();
+ for (SimpleAttributeDefinition attr : ServiceProviderDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, spAttributes, false, writer);
+ }
+ writeKeys(writer, spAttributes.get(Constants.Model.KEY));
+ writePrincipalNameMapping(writer, spAttributes);
+ writeRoleIdentifiers(writer, spAttributes);
+ writeIdentityProvider(writer, spAttributes.get(Constants.Model.IDENTITY_PROVIDER));
+
+ writer.writeEndElement();
+ }
+ }
+
+ void writeIdentityProvider(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+
+ for (Property idp : model.asPropertyList()) {
+ writer.writeStartElement(Constants.XML.IDENTITY_PROVIDER);
+ writer.writeAttribute(Constants.XML.ENTITY_ID, idp.getName());
+
+ ModelNode idpAttributes = idp.getValue();
+ for (SimpleAttributeDefinition attr : IdentityProviderDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, idpAttributes, false, writer);
+ }
+
+ writeSingleSignOn(writer, idpAttributes.get(Constants.Model.SINGLE_SIGN_ON));
+ writeSingleLogout(writer, idpAttributes.get(Constants.Model.SINGLE_LOGOUT));
+ writeKeys(writer, idpAttributes.get(Constants.Model.KEY));
+ }
+ writer.writeEndElement();
+ }
+
+ void writeSingleSignOn(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.SINGLE_SIGN_ON);
+ for (SimpleAttributeDefinition attr : SingleSignOnDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
writer.writeEndElement();
}
+ void writeSingleLogout(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.SINGLE_LOGOUT);
+ for (SimpleAttributeDefinition attr : SingleLogoutDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ void writeKeys(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ boolean contains = false;
+ for (Property key : model.asPropertyList()) {
+ if (!contains) {
+ writer.writeStartElement(Constants.XML.KEYS);
+ contains = true;
+ }
+ writer.writeStartElement(Constants.XML.KEY);
+ ModelNode keyAttributes = key.getValue();
+ for (SimpleAttributeDefinition attr : KeyDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, keyAttributes, false, writer);
+ }
+ for (SimpleAttributeDefinition attr : KeyDefinition.ELEMENTS) {
+ attr.getAttributeMarshaller().marshallAsElement(attr, keyAttributes, false, writer);
+ }
+ writeKeyStore(writer, keyAttributes.get(Constants.Model.KEY_STORE));
+
+ writer.writeEndElement();
+ }
+ if (contains) {
+ writer.writeEndElement();
+ }
+ }
+
+ void writeKeyStore(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.KEY_STORE);
+ for (SimpleAttributeDefinition attr : KeyStoreDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writePrivateKey(writer, model);
+ writeCertificate(writer, model);
+ writer.writeEndElement();
+ }
+
+ void writeCertificate(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode value = model.get(Constants.Model.CERTIFICATE_ALIAS);
+ if (!value.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.CERTIFICATE);
+ SimpleAttributeDefinition attr = KeyStoreCertificateDefinition.CERTIFICATE_ALIAS;
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ writer.writeEndElement();
+ }
+
+ void writePrivateKey(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode pk_alias = model.get(Constants.Model.PRIVATE_KEY_ALIAS);
+ ModelNode pk_password = model.get(Constants.Model.PRIVATE_KEY_PASSWORD);
+
+ if (!pk_alias.isDefined() && !pk_password.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.PRIVATE_KEY);
+ for (SimpleAttributeDefinition attr : KeyStorePrivateKeyDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ void writeRoleIdentifiers(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode value = model.get(Constants.Model.ROLE_ATTRIBUTES);
+ if (!value.isDefined()) {
+ return;
+ }
+
+ List<ModelNode> items = value.asList();
+ if (items.size() == 0) {
+ return;
+ }
+
+ writer.writeStartElement(Constants.XML.ROLE_IDENTIFIERS);
+ for (ModelNode item : items) {
+ writer.writeStartElement(Constants.XML.ATTRIBUTE);
+ writer.writeAttribute("name", item.asString());
+ writer.writeEndElement();
+ }
+ writer.writeEndElement();
+ }
+
+ void writePrincipalNameMapping(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ writer.writeStartElement(Constants.XML.PRINCIPAL_NAME_MAPPING);
+ ModelNode value = model.get(Constants.Model.PRINCIPAL_NAME_MAPPING_POLICY);
+ if (value.isDefined()) {
+ writer.writeAttribute(Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY, value.asString());
+ }
+ value = model.get(Constants.Model.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME);
+ if (value.isDefined()) {
+ writer.writeAttribute(Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, value.asString());
+ }
+ writer.writeEndElement();
+ }
}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java
new file mode 100644
index 0000000..0f76399
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ObjectTypeAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SIGNING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNING, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGNING)
+ .build();
+
+ static final SimpleAttributeDefinition ENCRYPTION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.ENCRYPTION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.ENCRYPTION)
+ .build();
+
+ static final SimpleAttributeDefinition PRIVATE_KEY_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_PEM)
+ .build();
+
+ static final SimpleAttributeDefinition PUBLIC_KEY_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PUBLIC_KEY_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PUBLIC_KEY_PEM)
+ .build();
+
+ static final SimpleAttributeDefinition CERTIFICATE_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.CERTIFICATE_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.CERTIFICATE_PEM)
+ .build();
+
+ static final ObjectTypeAttributeDefinition KEY_STORE =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.KEY_STORE,
+ KeyStoreDefinition.ALL_ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNING, ENCRYPTION};
+ static final SimpleAttributeDefinition[] ELEMENTS = {PRIVATE_KEY_PEM, PUBLIC_KEY_PEM, CERTIFICATE_PEM};
+ static final AttributeDefinition[] ALL_ATTRIBUTES = {SIGNING, ENCRYPTION, PRIVATE_KEY_PEM, PUBLIC_KEY_PEM, CERTIFICATE_PEM, KEY_STORE};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final HashMap<String, SimpleAttributeDefinition> ELEMENT_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ELEMENTS) {
+ ELEMENT_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final KeyDefinition INSTANCE = new KeyDefinition();
+
+ private KeyDefinition() {
+ super(PathElement.pathElement(Constants.Model.KEY),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.KEY),
+ new KeyAddHandler(),
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+
+ static SimpleAttributeDefinition lookupElement(String xmlName) {
+ return ELEMENT_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java
new file mode 100644
index 0000000..7ae2ff1
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyStoreCertificateDefinition {
+
+ static final SimpleAttributeDefinition CERTIFICATE_ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.CERTIFICATE_ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.CERTIFICATE_ALIAS)
+ .build();
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return Constants.XML.CERTIFICATE_ALIAS.equals(xmlName) ? CERTIFICATE_ALIAS : null;
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java
new file mode 100644
index 0000000..2fb14f5
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class KeyStoreDefinition {
+
+ static final SimpleAttributeDefinition RESOURCE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESOURCE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESOURCE)
+ .build();
+
+ static final SimpleAttributeDefinition PASSWORD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PASSWORD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PASSWORD)
+ .build();
+
+ static final SimpleAttributeDefinition FILE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.FILE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.FILE)
+ .build();
+
+ static final SimpleAttributeDefinition TYPE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.TYPE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.TYPE)
+ .build();
+
+ static final SimpleAttributeDefinition ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.ALIAS)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {RESOURCE, PASSWORD, FILE, TYPE, ALIAS};
+ static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {RESOURCE, PASSWORD, FILE, TYPE, ALIAS,
+ KeyStorePrivateKeyDefinition.PRIVATE_KEY_ALIAS,
+ KeyStorePrivateKeyDefinition.PRIVATE_KEY_PASSWORD,
+ KeyStoreCertificateDefinition.CERTIFICATE_ALIAS
+ };
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java
new file mode 100644
index 0000000..1947ca9
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyStorePrivateKeyDefinition {
+ static final SimpleAttributeDefinition PRIVATE_KEY_ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_ALIAS)
+ .build();
+
+ static final SimpleAttributeDefinition PRIVATE_KEY_PASSWORD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_PASSWORD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_PASSWORD)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {PRIVATE_KEY_ALIAS, PRIVATE_KEY_PASSWORD};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java
new file mode 100755
index 0000000..8bd7ac1
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/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.saml.as7.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 SAML subsystem override for deployment %s")
+ void deploymentSecured(String deployment);
+
+ @LogMessage(level = DEBUG)
+ @Message(value = "Keycloak SAML has overriden and secured deployment %s")
+ void warSecured(String deployment);
+
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java
new file mode 100755
index 0000000..f4917b1
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/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.saml.as7.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/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java
new file mode 100644
index 0000000..c5325f6
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+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;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class SecureDeploymentAddHandler extends AbstractAddStepHandler {
+
+ static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
+
+ private SecureDeploymentAddHandler() {
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java
new file mode 100644
index 0000000..75f4dca
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+/**
+ * Defines attributes and operations for a secure-deployment.
+ */
+public class SecureDeploymentDefinition extends SimpleResourceDefinition {
+
+ static final SecureDeploymentDefinition INSTANCE = new SecureDeploymentDefinition();
+
+ private SecureDeploymentDefinition() {
+ super(PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.SECURE_DEPLOYMENT),
+ SecureDeploymentAddHandler.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java
new file mode 100644
index 0000000..33d6015
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+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;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class ServiceProviderAddHandler extends AbstractAddStepHandler {
+
+ static final ServiceProviderAddHandler INSTANCE = new ServiceProviderAddHandler();
+
+ ServiceProviderAddHandler() {
+ super(ServiceProviderDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
new file mode 100644
index 0000000..02ecc8f
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ListAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.StringListAttributeDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ServiceProviderDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SSL_POLICY =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SSL_POLICY, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SSL_POLICY)
+ .build();
+
+ static final SimpleAttributeDefinition NAME_ID_POLICY_FORMAT =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.NAME_ID_POLICY_FORMAT, ModelType.STRING, true)
+ .setXmlName(Constants.XML.NAME_ID_POLICY_FORMAT)
+ .build();
+
+ static final SimpleAttributeDefinition LOGOUT_PAGE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.LOGOUT_PAGE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.LOGOUT_PAGE)
+ .build();
+
+ static final SimpleAttributeDefinition FORCE_AUTHENTICATION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.FORCE_AUTHENTICATION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.FORCE_AUTHENTICATION)
+ .build();
+
+ static final SimpleAttributeDefinition PRINCIPAL_NAME_MAPPING_POLICY =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRINCIPAL_NAME_MAPPING_POLICY, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY)
+ .build();
+
+ static final SimpleAttributeDefinition PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME)
+ .build();
+
+ static final ListAttributeDefinition ROLE_ATTRIBUTES =
+ new StringListAttributeDefinition.Builder(Constants.Model.ROLE_ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION};
+ static final AttributeDefinition[] ELEMENTS = {PRINCIPAL_NAME_MAPPING_POLICY, PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ROLE_ATTRIBUTES};
+
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+ static final HashMap<String, AttributeDefinition> ALL_MAP = new HashMap<>();
+ static final Collection<AttributeDefinition> ALL_ATTRIBUTES;
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+
+ ALL_MAP.putAll(ATTRIBUTE_MAP);
+ for (AttributeDefinition def : ELEMENTS) {
+ ALL_MAP.put(def.getXmlName(), def);
+ }
+ ALL_ATTRIBUTES = Collections.unmodifiableCollection(ALL_MAP.values());
+ }
+
+ static final ServiceProviderDefinition INSTANCE = new ServiceProviderDefinition();
+
+ private ServiceProviderDefinition() {
+ super(PathElement.pathElement(Constants.Model.SERVICE_PROVIDER),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.SERVICE_PROVIDER),
+ ServiceProviderAddHandler.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java
new file mode 100644
index 0000000..beb884c
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class SingleLogoutDefinition {
+
+ static final SimpleAttributeDefinition VALIDATE_REQUEST_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_REQUEST_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_REQUEST_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition VALIDATE_RESPONSE_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_RESPONSE_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition SIGN_REQUEST =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_REQUEST, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_REQUEST)
+ .build();
+
+ static final SimpleAttributeDefinition SIGN_RESPONSE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_RESPONSE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_RESPONSE)
+ .build();
+
+ static final SimpleAttributeDefinition REQUEST_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REQUEST_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition RESPONSE_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESPONSE_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESPONSE_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition POST_BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.POST_BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.POST_BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition REDIRECT_BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REDIRECT_BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REDIRECT_BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {VALIDATE_REQUEST_SIGNATURE, VALIDATE_RESPONSE_SIGNATURE,
+ SIGN_REQUEST, SIGN_RESPONSE, REQUEST_BINDING, RESPONSE_BINDING, POST_BINDING_URL, REDIRECT_BINDING_URL};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java
new file mode 100644
index 0000000..827be9b
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 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.saml.as7;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class SingleSignOnDefinition {
+
+ static final SimpleAttributeDefinition SIGN_REQUEST =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_REQUEST, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_REQUEST)
+ .build();
+
+ static final SimpleAttributeDefinition VALIDATE_RESPONSE_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_RESPONSE_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition REQUEST_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REQUEST_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition RESPONSE_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESPONSE_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESPONSE_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGN_REQUEST, VALIDATE_RESPONSE_SIGNATURE, REQUEST_BINDING, RESPONSE_BINDING, BINDING_URL};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java
new file mode 100644
index 0000000..dda3ed3
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java
@@ -0,0 +1,534 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.keycloak.subsystem.saml.as7.xml;
+
+import org.jboss.staxmapper.XMLExtendedStreamWriter;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.util.ArrayDeque;
+import java.util.Iterator;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+/**
+ * An XML stream writer which nicely formats the XML for configuration files.
+ *
+ * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
+ */
+public final class FormattingXMLStreamWriter implements XMLExtendedStreamWriter, XMLStreamConstants {
+ private static final String NO_NAMESPACE = new String();
+ private final XMLStreamWriter delegate;
+ private final ArrayDeque<ArgRunnable> attrQueue = new ArrayDeque<ArgRunnable>();
+ private int level;
+ private int state = START_DOCUMENT;
+ private boolean indentEndElement = false;
+ private ArrayDeque<String> unspecifiedNamespaces = new ArrayDeque<String>();
+
+
+ public FormattingXMLStreamWriter(final XMLStreamWriter delegate) {
+ this.delegate = delegate;
+ unspecifiedNamespaces.push(NO_NAMESPACE);
+ }
+
+ private void nl() throws XMLStreamException {
+ delegate.writeCharacters("\n");
+ }
+
+ private void indent() throws XMLStreamException {
+ int level = this.level;
+ final XMLStreamWriter delegate = this.delegate;
+ for (int i = 0; i < level; i ++) {
+ delegate.writeCharacters(" ");
+ }
+ }
+
+ private interface ArgRunnable {
+ public void run(int arg) throws XMLStreamException;
+ }
+
+ @Override
+ public void setUnspecifiedElementNamespace(final String namespace) {
+ ArrayDeque<String> namespaces = this.unspecifiedNamespaces;
+ namespaces.pop();
+ namespaces.push(namespace == null ? NO_NAMESPACE : namespace);
+ }
+
+ private String nestUnspecifiedNamespace() {
+ ArrayDeque<String> namespaces = unspecifiedNamespaces;
+ String clone = namespaces.getFirst();
+ namespaces.push(clone);
+ return clone;
+ }
+
+ @Override
+ public void writeStartElement(final String localName) throws XMLStreamException {
+ ArrayDeque<String> namespaces = unspecifiedNamespaces;
+ String namespace = namespaces.getFirst();
+ if (namespace != NO_NAMESPACE) {
+ writeStartElement(namespace, localName);
+ return;
+ }
+
+ unspecifiedNamespaces.push(namespace);
+
+ // If this is a nested element flush the outer
+ runAttrQueue();
+ nl();
+ indent();
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ if (arg == 0) {
+ delegate.writeStartElement(localName);
+ } else {
+ delegate.writeEmptyElement(localName);
+ }
+ }
+ });
+
+ level++;
+ state = START_ELEMENT;
+ indentEndElement = false;
+ }
+
+ @Override
+ public void writeStartElement(final String namespaceURI, final String localName) throws XMLStreamException {
+ nestUnspecifiedNamespace();
+
+ // If this is a nested element flush the outer
+ runAttrQueue();
+ nl();
+ indent();
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ if (arg == 0) {
+ delegate.writeStartElement(namespaceURI, localName);
+ } else {
+ delegate.writeEmptyElement(namespaceURI, localName);
+ }
+ }
+ });
+ level++;
+ state = START_ELEMENT;
+ indentEndElement = false;
+ }
+
+ @Override
+ public void writeStartElement(final String prefix, final String localName, final String namespaceURI) throws XMLStreamException {
+ nestUnspecifiedNamespace();
+
+ // If this is a nested element flush the outer
+ runAttrQueue();
+ nl();
+ indent();
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ if (arg == 0) {
+ delegate.writeStartElement(prefix, namespaceURI, localName);
+ } else {
+ delegate.writeEmptyElement(prefix, namespaceURI, localName);
+ }
+ }
+ });
+ level++;
+ state = START_ELEMENT;
+ indentEndElement = false;
+ }
+
+ @Override
+ public void writeEmptyElement(final String namespaceURI, final String localName) throws XMLStreamException {
+ runAttrQueue();
+ nl();
+ indent();
+ delegate.writeEmptyElement(namespaceURI, localName);
+ state = END_ELEMENT;
+ }
+
+ @Override
+ public void writeEmptyElement(final String prefix, final String localName, final String namespaceURI) throws XMLStreamException {
+ runAttrQueue();
+ nl();
+ indent();
+ delegate.writeEmptyElement(prefix, namespaceURI, localName);
+ state = END_ELEMENT;
+ }
+
+ @Override
+ public void writeEmptyElement(final String localName) throws XMLStreamException {
+ String namespace = unspecifiedNamespaces.getFirst();
+ if (namespace != NO_NAMESPACE) {
+ writeEmptyElement(namespace, localName);
+ return;
+ }
+
+ runAttrQueue();
+ nl();
+ indent();
+ delegate.writeEmptyElement(localName);
+ state = END_ELEMENT;
+ }
+
+ @Override
+ public void writeEndElement() throws XMLStreamException {
+ level--;
+ if (state != START_ELEMENT) {
+ runAttrQueue();
+ if (state != CHARACTERS || indentEndElement) {
+ nl();
+ indent();
+ indentEndElement = false;
+ }
+ delegate.writeEndElement();
+ } else {
+ // Change the start element to an empty element
+ ArgRunnable start = attrQueue.poll();
+ if (start == null) {
+ delegate.writeEndElement();
+ } else {
+ start.run(1);
+ // Write everything else
+ runAttrQueue();
+ }
+ }
+
+ unspecifiedNamespaces.pop();
+ state = END_ELEMENT;
+ }
+
+ private void runAttrQueue() throws XMLStreamException {
+ ArgRunnable attr;
+ while ((attr = attrQueue.poll()) != null) {
+ attr.run(0);
+ }
+ }
+
+ @Override
+ public void writeEndDocument() throws XMLStreamException {
+ delegate.writeEndDocument();
+ state = END_DOCUMENT;
+ }
+
+ @Override
+ public void close() throws XMLStreamException {
+ delegate.close();
+ state = END_DOCUMENT;
+ }
+
+ @Override
+ public void flush() throws XMLStreamException {
+ delegate.flush();
+ }
+
+ @Override
+ public void writeAttribute(final String localName, final String value) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ try {
+ delegate.writeAttribute(localName, value);
+ } catch (XMLStreamException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(prefix, namespaceURI, localName, value);
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String namespaceURI, final String localName, final String value) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(namespaceURI, localName, value);
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String localName, final String[] values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String[] values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(prefix, namespaceURI, localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String namespaceURI, final String localName, final String[] values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(namespaceURI, localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String localName, final Iterable<String> values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String prefix, final String namespaceURI, final String localName, final Iterable<String> values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(prefix, namespaceURI, localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeAttribute(final String namespaceURI, final String localName, final Iterable<String> values) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeAttribute(namespaceURI, localName, join(values));
+ }
+ });
+ }
+
+ @Override
+ public void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeNamespace(prefix, namespaceURI);
+ }
+ });
+ }
+
+ @Override
+ public void writeDefaultNamespace(final String namespaceURI) throws XMLStreamException {
+ attrQueue.add(new ArgRunnable() {
+ public void run(int arg) throws XMLStreamException {
+ delegate.writeDefaultNamespace(namespaceURI);
+ }
+ });
+ }
+
+ @Override
+ public void writeComment(final String data) throws XMLStreamException {
+ runAttrQueue();
+ nl();
+ nl();
+ indent();
+ final StringBuilder b = new StringBuilder(data.length());
+ final Iterator<String> i = Spliterator.over(data, '\n');
+ if (! i.hasNext()) {
+ return;
+ } else {
+ final String first = i.next();
+ if (! i.hasNext()) {
+ delegate.writeComment(" " + first + " ");
+ state = COMMENT;
+ return;
+ } else {
+ b.append('\n');
+ for (int q = 0; q < level; q++) {
+ b.append(" ");
+ }
+ b.append(" ~ ");
+ b.append(first);
+ do {
+ b.append('\n');
+ for (int q = 0; q < level; q++) {
+ b.append(" ");
+ }
+ b.append(" ~ ");
+ b.append(i.next());
+ } while (i.hasNext());
+ }
+ b.append('\n');
+ for (int q = 0; q < level; q ++) {
+ b.append(" ");
+ }
+ b.append(" ");
+ delegate.writeComment(b.toString());
+ state = COMMENT;
+ }
+ }
+
+ @Override
+ public void writeProcessingInstruction(final String target) throws XMLStreamException {
+ runAttrQueue();
+ nl();
+ indent();
+ delegate.writeProcessingInstruction(target);
+ state = PROCESSING_INSTRUCTION;
+ }
+
+ @Override
+ public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
+ runAttrQueue();
+ nl();
+ indent();
+ delegate.writeProcessingInstruction(target, data);
+ state = PROCESSING_INSTRUCTION;
+ }
+
+ @Override
+ public void writeCData(final String data) throws XMLStreamException {
+ runAttrQueue();
+ delegate.writeCData(data);
+ state = CDATA;
+ }
+
+ @Override
+ public void writeDTD(final String dtd) throws XMLStreamException {
+ nl();
+ indent();
+ delegate.writeDTD(dtd);
+ state = DTD;
+ }
+
+ @Override
+ public void writeEntityRef(final String name) throws XMLStreamException {
+ runAttrQueue();
+ delegate.writeEntityRef(name);
+ state = ENTITY_REFERENCE;
+ }
+
+ @Override
+ public void writeStartDocument() throws XMLStreamException {
+ delegate.writeStartDocument();
+ nl();
+ state = START_DOCUMENT;
+ }
+
+ @Override
+ public void writeStartDocument(final String version) throws XMLStreamException {
+ delegate.writeStartDocument(version);
+ nl();
+ state = START_DOCUMENT;
+ }
+
+ @Override
+ public void writeStartDocument(final String encoding, final String version) throws XMLStreamException {
+ delegate.writeStartDocument(encoding, version);
+ nl();
+ state = START_DOCUMENT;
+ }
+
+ @Override
+ public void writeCharacters(final String text) throws XMLStreamException {
+ runAttrQueue();
+ if (state != CHARACTERS) {
+ nl();
+ indent();
+ }
+ final Iterator<String> iterator = Spliterator.over(text, '\n');
+ while (iterator.hasNext()) {
+ final String t = iterator.next();
+ delegate.writeCharacters(t);
+ if (iterator.hasNext()) {
+ nl();
+ indent();
+ }
+ }
+ state = CHARACTERS;
+ indentEndElement = true;
+ }
+
+ @Override
+ public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
+ runAttrQueue();
+ delegate.writeCharacters(text, start, len);
+ state = CHARACTERS;
+ }
+
+ @Override
+ public String getPrefix(final String uri) throws XMLStreamException {
+ return delegate.getPrefix(uri);
+ }
+
+ @Override
+ public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
+ delegate.setPrefix(prefix, uri);
+ }
+
+ @Override
+ public void setDefaultNamespace(final String uri) throws XMLStreamException {
+ runAttrQueue();
+ delegate.setDefaultNamespace(uri);
+ }
+
+ @Override
+ public void setNamespaceContext(final NamespaceContext context) throws XMLStreamException {
+ delegate.setNamespaceContext(context);
+ }
+
+ @Override
+ public NamespaceContext getNamespaceContext() {
+ return delegate.getNamespaceContext();
+ }
+
+ @Override
+ public Object getProperty(final String name) throws IllegalArgumentException {
+ return delegate.getProperty(name);
+ }
+
+ private static String join(final String[] values) {
+ final StringBuilder b = new StringBuilder();
+ for (int i = 0, valuesLength = values.length; i < valuesLength; i++) {
+ final String s = values[i];
+ if (s != null) {
+ if (i > 0) {
+ b.append(' ');
+ }
+ b.append(s);
+ }
+ }
+ return b.toString();
+ }
+
+ private static String join(final Iterable<String> values) {
+ final StringBuilder b = new StringBuilder();
+ Iterator<String> iterator = values.iterator();
+ while (iterator.hasNext()) {
+ final String s = iterator.next();
+ if (s != null) {
+ b.append(s);
+ if (iterator.hasNext()) b.append(' ');
+ }
+ }
+ return b.toString();
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java
new file mode 100644
index 0000000..e9cb5c6
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.keycloak.subsystem.saml.as7.xml;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
+ */
+final class Spliterator implements Iterator<String> {
+ private final String subject;
+ private final char delimiter;
+ private int i;
+
+ Spliterator(final String subject, final char delimiter) {
+ this.subject = subject;
+ this.delimiter = delimiter;
+ i = 0;
+ }
+
+ static Spliterator over(String subject, char delimiter) {
+ return new Spliterator(subject, delimiter);
+ }
+
+ public boolean hasNext() {
+ return i != -1;
+ }
+
+ public String next() {
+ final int i = this.i;
+ if (i == -1) {
+ throw new NoSuchElementException();
+ }
+ int n = subject.indexOf(delimiter, i);
+ try {
+ return n == -1 ? subject.substring(i) : subject.substring(i, n);
+ } finally {
+ this.i = n == -1 ? -1 : n + 1;
+ }
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties b/saml/client-adapter/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
new file mode 100755
index 0000000..f8a4a11
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
@@ -0,0 +1,63 @@
+keycloak-saml.subsystem=Keycloak adapter subsystem
+keycloak-saml.subsystem.add=Operation Adds Keycloak adapter subsystem
+keycloak-saml.subsystem.remove=Operation removes Keycloak adapter subsystem
+keycloak-saml.subsystem.secure-deployment=A deployment secured by Keycloak.
+
+keycloak-saml.secure-deployment=A deployment secured by Keycloak
+keycloak-saml.secure-deployment.add=Add a deployment to be secured by Keycloak
+keycloak-saml.secure-deployment.remove=Remove a deployment to be secured by Keycloak
+keycloak-saml.secure-deployment.service-provider=A security provider configuration for secure deployment
+
+keycloak-saml.service-provider=A security provider configuration for secure deployment
+keycloak-saml.service-provider.add=Add a security provider configuration to deployment secured by Keycloak SAML
+keycloak-saml.service-provider.remove=Remove a security provider definition from deployment secured by Keycloak SAML
+keycloak-saml.service-provider.ssl-policy=SSL Policy to use
+keycloak-saml.service-provider.name-id-policy-format=Name ID policy format URN
+keycloak-saml.service-provider.logout-page=URI to a logout page
+keycloak-saml.service-provider.force-authentication=Redirected unauthenticated request to a login page
+keycloak-saml.service-provider.role-attributes=Role identifiers
+keycloak-saml.service-provider.principal-name-mapping-policy=Principal name mapping policy
+keycloak-saml.service-provider.principal-name-mapping-attribute-name=Principal name mapping attribute name
+keycloak-saml.service-provider.key=A key definition
+keycloak-saml.service-provider.identity-provider=Identity provider definition
+
+keycloak-saml.key=A key configuration for service provider or identity provider
+keycloak-saml.key.add=Add a key definition
+keycloak-saml.key.remove=Remove a key definition
+keycloak-saml.key.signing=Key can be used for signing
+keycloak-saml.key.encryption=Key can be used for encryption
+keycloak-saml.key.private-key-pem=Private key string in pem format
+keycloak-saml.key.public-key-pem=Public key string in pem format
+keycloak-saml.key.certificate-pem=Certificate key string in pem format
+keycloak-saml.key.key-store=Key store definition
+keycloak-saml.key.key-store.file=Key store filesystem path
+keycloak-saml.key.key-store.resource=Key store resource URI
+keycloak-saml.key.key-store.password=Key store password
+keycloak-saml.key.key-store.type=Key store format
+keycloak-saml.key.key-store.alias=Key alias
+keycloak-saml.key.key-store.private-key-alias=Private key alias
+keycloak-saml.key.key-store.private-key-password=Private key password
+keycloak-saml.key.key-store.certificate-alias=Certificate alias
+
+keycloak-saml.identity-provider=An identity provider configuration
+keycloak-saml.identity-provider.add=Add an identity provider
+keycloak-saml.identity-provider.remove=Remove an identity provider
+keycloak-saml.identity-provider.signatures-required=Require signatures for single-sign-on and single-logout
+keycloak-saml.identity-provider.signature-algorithm=Signature algorithm
+keycloak-saml.identity-provider.signature-canonicalization-method=Signature canonicalization method
+keycloak-saml.identity-provider.single-sign-on=Single sign-on configuration
+keycloak-saml.identity-provider.single-sign-on.sign-request=Sign SSO requests
+keycloak-saml.identity-provider.single-sign-on.validate-response-signature=Validate an SSO response signature
+keycloak-saml.identity-provider.single-sign-on.request-binding=HTTP method to use for requests
+keycloak-saml.identity-provider.single-sign-on.response-binding=HTTP method to use for responses
+keycloak-saml.identity-provider.single-sign-on.binding-url=SSO endpoint URL
+keycloak-saml.identity-provider.single-logout=Single logout configuration
+keycloak-saml.identity-provider.single-logout.validate-request-signature=Validate a single-logout request signature
+keycloak-saml.identity-provider.single-logout.validate-response-signature=Validate a single-logout response signature
+keycloak-saml.identity-provider.single-logout.sign-request=Sign single-logout requests
+keycloak-saml.identity-provider.single-logout.sign-response=Sign single-logout responses
+keycloak-saml.identity-provider.single-logout.request-binding=HTTP method to use for request
+keycloak-saml.identity-provider.single-logout.response-binding=HTTP method to use for response
+keycloak-saml.identity-provider.single-logout.post-binding-url=Endpoint URL for posting
+keycloak-saml.identity-provider.single-logout.redirect-binding-url=Endpoint URL for redirects
+keycloak-saml.identity-provider.key=Key definition for identity provider
\ No newline at end of file
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd b/saml/client-adapter/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
new file mode 100644
index 0000000..725104b
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="urn:jboss:domain:keycloak-saml:1.1"
+ xmlns="urn:jboss:domain:keycloak-saml:1.1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1.0">
+
+ <!-- The subsystem root element -->
+ <xs:element name="subsystem" type="subsystem-type"/>
+
+ <xs:complexType name="subsystem-type">
+ <xs:annotation>
+ <xs:documentation>
+ <![CDATA[
+ The Keycloak SAML adapter subsystem, used to register deployments managed by Keycloak SAML adapter
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:all>
+ <xs:element name="secure-deployment" minOccurs="0" type="secure-deployment-type"/>
+ </xs:all>
+ </xs:complexType>
+
+ <xs:complexType name="secure-deployment-type">
+ <xs:all>
+ <xs:element name="SP" minOccurs="1" maxOccurs="1" type="sp-type"/>
+ </xs:all>
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The name of the realm.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="sp-type">
+ <xs:all>
+ <xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
+ <xs:element name="PrincipalNameMapping" minOccurs="0" maxOccurs="1" type="principal-name-mapping-type"/>
+ <xs:element name="RoleIdentifiers" minOccurs="0" maxOccurs="1" type="role-identifiers-type"/>
+ <xs:element name="IDP" minOccurs="1" maxOccurs="1" type="identity-provider-type"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The entity ID for SAML service provider</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="sslPolicy" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The ssl policy</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Name ID policy format URN</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="logoutPage" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>URI to a logout page</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="forceAuthentication" type="xs:boolean" use="required">
+ <xs:annotation>
+ <xs:documentation>Redirected unauthenticated request to a login page</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="identity-provider-type">
+ <xs:all minOccurs="1" maxOccurs="1">
+ <xs:element name="SingleSignOnService" minOccurs="1" maxOccurs="1" type="single-signon-type"/>
+ <xs:element name="SingleLogoutService" minOccurs="0" maxOccurs="1" type="single-logout-type"/>
+ <xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The entity ID for SAML service provider</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signaturesRequired" type="xs:boolean" use="required">
+ <xs:annotation>
+ <xs:documentation>Require signatures for single-sign-on and single-logout</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureAlgorithm" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Algorithm used for signatures</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Canonicalization method used for signatures</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="single-signon-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign the SSO requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate the SSO response signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for response</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="bindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>SSO endpoint URL</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="single-logout-type">
+ <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate a single-logout request signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate a single-logout response signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign single-logout requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signResponse" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign single-logout responses</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for request</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for response</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="postBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Endpoint URL for posting</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Endpoint URL for redirects</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="keys-type">
+ <xs:sequence>
+ <xs:element name="Key" minOccurs="1" maxOccurs="2" type="key-type"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="key-type">
+ <xs:all>
+ <xs:element name="KeyStore" minOccurs="0" maxOccurs="1" type="keystore-type"/>
+ <xs:element name="PrivateKeyPem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ <xs:element name="PublicKeyPem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ <xs:element name="CertificatePem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ </xs:all>
+ <xs:attribute name="signing" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key can be used for signing</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key can be used for encryption</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="keystore-type">
+ <xs:sequence minOccurs="0" maxOccurs="1">
+ <xs:element name="PrivateKey" minOccurs="0" maxOccurs="1" type="privatekey-type"/>
+ <xs:element name="Certificate" minOccurs="0" maxOccurs="1" type="certificate-type"/>
+ </xs:sequence>
+ <xs:attribute name="file" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store filesystem path</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="resource" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store resource URI</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Key store password</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store format</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="alias" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="privatekey-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Private key alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Private key password</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="certificate-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Certificate alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="principal-name-mapping-type">
+ <xs:attribute name="policy" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Principal name mapping policy. Possible values: FROM_NAME_ID</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="attribute" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Name of the attribute to use for principal name mapping</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="role-identifiers-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Attribute" minOccurs="0" maxOccurs="unbounded" type="attribute-type"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="attribute-type">
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Role attribute</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+</xs:schema>
diff --git a/saml/client-adapter/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml b/saml/client-adapter/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml
new file mode 100644
index 0000000..19d7528
--- /dev/null
+++ b/saml/client-adapter/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml
@@ -0,0 +1,49 @@
+<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ <secure-deployment name="my-app.war">
+ <SP entityID="http://localhost:8080/sales-post-enc/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+
+ <Keys>
+ <Key encryption="true" signing="true">
+ <PrivateKeyPem>my_key.pem</PrivateKeyPem>
+ <PublicKeyPem>my_key.pub</PublicKeyPem>
+ <CertificatePem>cert.cer</CertificatePem>
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ <Attribute name="Role2"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <Keys>
+ <Key signing="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="saml-demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
diff --git a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
index 280b5f8..e921d15 100755
--- a/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
+++ b/saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/AdapterConstants.java
@@ -5,5 +5,5 @@ package org.keycloak.adapters.saml;
* @version $Revision: 1 $
*/
public class AdapterConstants {
- public static final String AUTH_DATA_PARAM_NAME="org.keycloak.auth.deployment.data";
+ public static final String AUTH_DATA_PARAM_NAME="org.keycloak.saml.xml.adapterConfig";
}
diff --git a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
index 615e4a6..ad22718 100755
--- a/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
+++ b/saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
@@ -28,6 +28,7 @@ import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.LoginConfig;
import io.undertow.servlet.api.ServletSessionConfig;
import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.AdapterConstants;
import org.keycloak.adapters.saml.DefaultSamlDeployment;
import org.keycloak.adapters.saml.SamlConfigResolver;
import org.keycloak.adapters.saml.SamlDeployment;
@@ -38,6 +39,7 @@ import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
import org.keycloak.saml.common.exceptions.ParsingException;
import javax.servlet.ServletContext;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
@@ -64,8 +66,16 @@ public class SamlServletExtension implements ServletExtension {
return false;
}
+ private static InputStream getXMLFromServletContext(ServletContext servletContext) {
+ String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
+ if (json == null) {
+ return null;
+ }
+ return new ByteArrayInputStream(json.getBytes());
+ }
+
private static InputStream getConfigInputStream(ServletContext context) {
- InputStream is = null;
+ InputStream is = getXMLFromServletContext(context);
if (is == null) {
String path = context.getInitParameter("keycloak.config.file");
if (path == null) {
saml/client-adapter/wildfly/pom.xml 2(+1 -1)
diff --git a/saml/client-adapter/wildfly/pom.xml b/saml/client-adapter/wildfly/pom.xml
index 2b5eb25..d9014b6 100755
--- a/saml/client-adapter/wildfly/pom.xml
+++ b/saml/client-adapter/wildfly/pom.xml
@@ -15,6 +15,6 @@
<modules>
<module>wildfly-adapter</module>
- <module>wildfly9-subsystem</module>
+ <module>wildfly-subsystem</module>
</modules>
</project>
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java
new file mode 100644
index 0000000..e364e2d
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class Configuration {
+
+ static Configuration INSTANCE = new Configuration();
+
+ private ModelNode config = new ModelNode();
+
+ private Configuration() {
+ }
+
+ void updateModel(ModelNode operation, ModelNode model) {
+ ModelNode node = config;
+ ModelNode addr = operation.get("address");
+ for (Property item : addr.asPropertyList()) {
+ node = getNodeForAddressElement(node, item);
+ }
+ node.set(model);
+ }
+
+ private ModelNode getNodeForAddressElement(ModelNode node, Property item) {
+ String key = item.getValue().asString();
+ ModelNode keymodel = node.get(item.getName());
+ return keymodel.get(key);
+ }
+
+ public ModelNode getSecureDeployment(String name) {
+ ModelNode secureDeployment = config.get("subsystem").get("keycloak-saml").get(Constants.Model.SECURE_DEPLOYMENT);
+ if (secureDeployment.hasDefined(name)) {
+ return secureDeployment.get(name);
+ }
+ return null;
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
new file mode 100644
index 0000000..9b89fb2
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class Constants {
+
+ static class Model {
+ static final String SECURE_DEPLOYMENT = "secure-deployment";
+ static final String SERVICE_PROVIDER = "service-provider";
+
+ static final String SSL_POLICY = "ssl-policy";
+ static final String NAME_ID_POLICY_FORMAT = "name-id-policy-format";
+ static final String LOGOUT_PAGE = "logout-page";
+ static final String FORCE_AUTHENTICATION = "force-authentication";
+ static final String ROLE_ATTRIBUTES = "role-attributes";
+ static final String SIGNING = "signing";
+ static final String ENCRYPTION = "encryption";
+ static final String KEY = "key";
+ static final String RESOURCE = "resource";
+ static final String PASSWORD = "password";
+
+ static final String PRIVATE_KEY_ALIAS = "private-key-alias";
+ static final String PRIVATE_KEY_PASSWORD = "private-key-password";
+ static final String CERTIFICATE_ALIAS = "certificate-alias";
+ static final String KEY_STORE = "key-store";
+ static final String SIGN_REQUEST = "sign-request";
+ static final String VALIDATE_RESPONSE_SIGNATURE = "validate-response-signature";
+ static final String REQUEST_BINDING = "request-binding";
+ static final String BINDING_URL = "binding-url";
+ static final String VALIDATE_REQUEST_SIGNATURE = "validate-request-signature";
+ static final String SIGN_RESPONSE = "sign-response";
+ static final String RESPONSE_BINDING = "response-binding";
+ static final String POST_BINDING_URL = "post-binding-url";
+ static final String REDIRECT_BINDING_URL = "redirect-binding-url";
+ static final String SINGLE_SIGN_ON = "single-sign-on";
+ static final String SINGLE_LOGOUT = "single-logout";
+ static final String IDENTITY_PROVIDER = "identity-provider";
+ static final String PRINCIPAL_NAME_MAPPING_POLICY = "principal-name-mapping-policy";
+ static final String PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME = "principal-name-mapping-attribute-name";
+ static final String SIGNATURE_ALGORITHM = "signature-algorithm";
+ static final String SIGNATURE_CANONICALIZATION_METHOD = "signature-canonicalization-method";
+ static final String PRIVATE_KEY_PEM = "private-key-pem";
+ static final String PUBLIC_KEY_PEM = "public-key-pem";
+ static final String CERTIFICATE_PEM = "certificate-pem";
+ static final String TYPE = "type";
+ static final String ALIAS = "alias";
+ static final String FILE = "file";
+ static final String SIGNATURES_REQUIRED = "signatures-required";
+ }
+
+
+ static class XML {
+ static final String SECURE_DEPLOYMENT = "secure-deployment";
+ static final String SERVICE_PROVIDER = "SP";
+
+ static final String NAME = "name";
+ static final String ENTITY_ID = "entityID";
+ static final String SSL_POLICY = "sslPolicy";
+ static final String NAME_ID_POLICY_FORMAT = "nameIDPolicyFormat";
+ static final String LOGOUT_PAGE = "logoutPage";
+ static final String FORCE_AUTHENTICATION = "forceAuthentication";
+ static final String ROLE_IDENTIFIERS = "RoleIdentifiers";
+ static final String SIGNING = "signing";
+ static final String ENCRYPTION = "encryption";
+ static final String KEYS = "Keys";
+ static final String KEY = "Key";
+ static final String RESOURCE = "resource";
+ static final String PASSWORD = "password";
+ static final String KEY_STORE = "KeyStore";
+ static final String PRIVATE_KEY = "PrivateKey";
+ static final String CERTIFICATE = "Certificate";
+
+ static final String PRIVATE_KEY_ALIAS = "alias";
+ static final String PRIVATE_KEY_PASSWORD = "password";
+ static final String CERTIFICATE_ALIAS = "alias";
+ static final String SIGN_REQUEST = "signRequest";
+ static final String VALIDATE_RESPONSE_SIGNATURE = "validateResponseSignature";
+ static final String REQUEST_BINDING = "requestBinding";
+ static final String BINDING_URL = "bindingUrl";
+ static final String VALIDATE_REQUEST_SIGNATURE = "validateRequestSignature";
+ static final String SIGN_RESPONSE = "signResponse";
+ static final String RESPONSE_BINDING = "responseBinding";
+ static final String POST_BINDING_URL = "postBindingUrl";
+ static final String REDIRECT_BINDING_URL = "redirectBindingUrl";
+ static final String SINGLE_SIGN_ON = "SingleSignOnService";
+ static final String SINGLE_LOGOUT = "SingleLogoutService";
+ static final String IDENTITY_PROVIDER = "IDP";
+ static final String PRINCIPAL_NAME_MAPPING = "PrincipalNameMapping";
+ static final String PRINCIPAL_NAME_MAPPING_POLICY = "policy";
+ static final String PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME = "attribute";
+ static final String ATTRIBUTE = "Attribute";
+ static final String SIGNATURE_ALGORITHM = "signatureAlgorithm";
+ static final String SIGNATURE_CANONICALIZATION_METHOD = "signatureCanonicalizationMethod";
+ static final String PRIVATE_KEY_PEM = "PrivateKeyPem";
+ static final String PUBLIC_KEY_PEM = "PublicKeyPem";
+ static final String CERTIFICATE_PEM = "CertificatePem";
+ static final String TYPE = "type";
+ static final String ALIAS = "alias";
+ static final String FILE = "file";
+ static final String SIGNATURES_REQUIRED = "signaturesRequired";
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java
new file mode 100644
index 0000000..7648ba3
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class IdentityProviderAddHandler extends AbstractAddStepHandler {
+
+ IdentityProviderAddHandler() {
+ super(IdentityProviderDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java
new file mode 100644
index 0000000..1459f15
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ObjectTypeAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class IdentityProviderDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SIGNATURES_REQUIRED =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURES_REQUIRED, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGNATURES_REQUIRED)
+ .build();
+
+ static final SimpleAttributeDefinition SIGNATURE_ALGORITHM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURE_ALGORITHM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SIGNATURE_ALGORITHM)
+ .build();
+
+ static final SimpleAttributeDefinition SIGNATURE_CANONICALIZATION_METHOD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNATURE_CANONICALIZATION_METHOD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SIGNATURE_CANONICALIZATION_METHOD)
+ .build();
+
+ static final ObjectTypeAttributeDefinition SINGLE_SIGN_ON =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.SINGLE_SIGN_ON,
+ SingleSignOnDefinition.ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final ObjectTypeAttributeDefinition SINGLE_LOGOUT =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.SINGLE_LOGOUT,
+ SingleLogoutDefinition.ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD};
+
+ static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {SIGNATURES_REQUIRED, SIGNATURE_ALGORITHM, SIGNATURE_CANONICALIZATION_METHOD, SINGLE_SIGN_ON, SINGLE_LOGOUT};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final IdentityProviderDefinition INSTANCE = new IdentityProviderDefinition();
+
+ private IdentityProviderDefinition() {
+ super(PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.IDENTITY_PROVIDER),
+ new IdentityProviderAddHandler(),
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
\ No newline at end of file
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java
new file mode 100644
index 0000000..1799290
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class KeyAddHandler extends AbstractAddStepHandler {
+
+ KeyAddHandler() {
+ super(KeyDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java
new file mode 100755
index 0000000..8b20565
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+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.operations.common.Util;
+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.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * The subsystem parser, which uses stax to read and write to and from xml
+ */
+class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<List<ModelNode>>, XMLElementWriter<SubsystemMarshallingContext> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> list) throws XMLStreamException {
+ // Require no attributes
+ ParseUtils.requireNoAttributes(reader);
+ ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakSamlExtension.PATH_SUBSYSTEM));
+ list.add(addKeycloakSub);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ if (reader.getLocalName().equals(Constants.XML.SECURE_DEPLOYMENT)) {
+ readSecureDeployment(reader, list);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ // used for debugging
+ private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException {
+ return reader.nextTag();
+ }
+
+ void readSecureDeployment(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
+ String name = readRequiredAttribute(reader, Constants.XML.NAME);
+
+ PathAddress addr = PathAddress.pathAddress(
+ PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakSamlExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT, name));
+ ModelNode addSecureDeployment = Util.createAddOperation(addr);
+ list.add(addSecureDeployment);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (tagName.equals(Constants.XML.SERVICE_PROVIDER)) {
+ readServiceProvider(reader, list, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readServiceProvider(XMLExtendedStreamReader reader, List<ModelNode> list, PathAddress parentAddr) throws XMLStreamException {
+ String entityId = readRequiredAttribute(reader, Constants.XML.ENTITY_ID);
+
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.SERVICE_PROVIDER, entityId));
+ ModelNode addServiceProvider = Util.createAddOperation(addr);
+ list.add(addServiceProvider);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ if (Constants.XML.ENTITY_ID.equals(name)) {
+ continue;
+ }
+
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = ServiceProviderDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addServiceProvider, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.KEYS.equals(tagName)) {
+ readKeys(list, reader, addr);
+ } else if (Constants.XML.PRINCIPAL_NAME_MAPPING.equals(tagName)) {
+ readPrincipalNameMapping(addServiceProvider, reader);
+ } else if (Constants.XML.ROLE_IDENTIFIERS.equals(tagName)) {
+ readRoleIdentifiers(addServiceProvider, reader);
+ } else if (Constants.XML.IDENTITY_PROVIDER.equals(tagName)) {
+ readIdentityProvider(list, reader, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readIdentityProvider(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ String entityId = readRequiredAttribute(reader, Constants.XML.ENTITY_ID);
+
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.IDENTITY_PROVIDER, entityId));
+ ModelNode addIdentityProvider = Util.createAddOperation(addr);
+ list.add(addIdentityProvider);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ if (Constants.XML.ENTITY_ID.equals(name)
+ // don't break if encountering this noop attr from client-adapter/core keycloak_saml_adapter_1_6.xsd
+ || "encryption".equals(name)) {
+ continue;
+ }
+ SimpleAttributeDefinition attr = IdentityProviderDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addIdentityProvider, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.SINGLE_SIGN_ON.equals(tagName)) {
+ readSingleSignOn(addIdentityProvider, reader);
+ } else if (Constants.XML.SINGLE_LOGOUT.equals(tagName)) {
+ readSingleLogout(addIdentityProvider, reader);
+ } else if (Constants.XML.KEYS.equals(tagName)) {
+ readKeys(list, reader, addr);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readSingleSignOn(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode sso = addIdentityProvider.get(Constants.Model.SINGLE_SIGN_ON);
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = SingleSignOnDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, sso, reader);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readSingleLogout(ModelNode addIdentityProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode slo = addIdentityProvider.get(Constants.Model.SINGLE_LOGOUT);
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = SingleLogoutDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, slo, reader);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readKeys(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+ List<ModelNode> keyList = new LinkedList<>();
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (!Constants.XML.KEY.equals(tagName)) {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ readKey(keyList, reader, parentAddr);
+ }
+ list.addAll(keyList);
+ }
+
+ void readKey(List<ModelNode> list, XMLExtendedStreamReader reader, PathAddress parentAddr) throws XMLStreamException {
+ PathAddress addr = PathAddress.pathAddress(parentAddr,
+ PathElement.pathElement(Constants.Model.KEY, "key-" + list.size()));
+ ModelNode addKey = Util.createAddOperation(addr);
+ list.add(addKey);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKey, reader);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (Constants.XML.KEY_STORE.equals(tagName)) {
+ readKeyStore(addKey, reader);
+ } else if (Constants.XML.PRIVATE_KEY_PEM.equals(tagName)
+ || Constants.XML.PUBLIC_KEY_PEM.equals(tagName)
+ || Constants.XML.CERTIFICATE_PEM.equals(tagName)) {
+
+ readNoAttrElementContent(KeyDefinition.lookupElement(tagName), addKey, reader);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+ void readNoAttrElementContent(SimpleAttributeDefinition attr, ModelNode model, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+ String value = reader.getElementText();
+ attr.parseAndSetParameter(value, model, reader);
+ }
+
+ void readKeyStore(ModelNode addKey, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ModelNode addKeyStore = addKey.get(Constants.Model.KEY_STORE);
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStoreDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.FILE) && !addKeyStore.hasDefined(Constants.Model.RESOURCE)) {
+ throw new XMLStreamException("KeyStore element must have 'file' or 'resource' attribute set", reader.getLocation());
+ }
+ if (!addKeyStore.hasDefined(Constants.Model.PASSWORD)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PASSWORD);
+ }
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (Constants.XML.PRIVATE_KEY.equals(tagName)) {
+ readPrivateKey(reader, addKeyStore);
+ } else if (Constants.XML.CERTIFICATE.equals(tagName)) {
+ readCertificate(reader, addKeyStore);
+ } else {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+ }
+ }
+
+
+ void readPrivateKey(XMLExtendedStreamReader reader, ModelNode addKeyStore) throws XMLStreamException {
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStorePrivateKeyDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.PRIVATE_KEY_ALIAS)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRIVATE_KEY_ALIAS);
+ }
+ if (!addKeyStore.hasDefined(Constants.Model.PRIVATE_KEY_PASSWORD)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRIVATE_KEY_PASSWORD);
+ }
+
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readCertificate(XMLExtendedStreamReader reader, ModelNode addKeyStore) throws XMLStreamException {
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ SimpleAttributeDefinition attr = KeyStoreCertificateDefinition.lookup(name);
+ if (attr == null) {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ attr.parseAndSetParameter(value, addKeyStore, reader);
+ }
+
+ if (!addKeyStore.hasDefined(Constants.Model.CERTIFICATE_ALIAS)) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.CERTIFICATE_ALIAS);
+ }
+
+ ParseUtils.requireNoContent(reader);
+ }
+
+ void readRoleIdentifiers(ModelNode addServiceProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+ ParseUtils.requireNoAttributes(reader);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+
+ if (!Constants.XML.ATTRIBUTE.equals(tagName)) {
+ throw ParseUtils.unexpectedElement(reader);
+ }
+
+ ParseUtils.requireSingleAttribute(reader, Constants.XML.NAME);
+ String name = ParseUtils.readStringAttributeElement(reader, Constants.XML.NAME);
+
+ ServiceProviderDefinition.ROLE_ATTRIBUTES.parseAndAddParameterElement(name, addServiceProvider, reader);
+ }
+ }
+
+ void readPrincipalNameMapping(ModelNode addServiceProvider, XMLExtendedStreamReader reader) throws XMLStreamException {
+
+ boolean policySet = false;
+
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String name = reader.getAttributeLocalName(i);
+ String value = reader.getAttributeValue(i);
+
+ if (Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY.equals(name)) {
+ policySet = true;
+ ServiceProviderDefinition.PRINCIPAL_NAME_MAPPING_POLICY.parseAndSetParameter(value, addServiceProvider, reader);
+ } else if (Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME.equals(name)) {
+ ServiceProviderDefinition.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME.parseAndSetParameter(value, addServiceProvider, reader);
+ } else {
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ }
+
+ if (!policySet) {
+ throw ParseUtils.missingRequired(reader, Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY);
+ }
+ ParseUtils.requireNoContent(reader);
+ }
+
+ /**
+ * Read an attribute, and throw exception if attribute is not present
+ */
+ String readRequiredAttribute(XMLExtendedStreamReader reader, String attrName) throws XMLStreamException {
+ String value = null;
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String attr = reader.getAttributeLocalName(i);
+ if (attr.equals(attrName)) {
+ value = reader.getAttributeValue(i);
+ break;
+ }
+ }
+ if (value == null) {
+ throw ParseUtils.missingRequired(reader, Collections.singleton(attrName));
+ }
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
+ context.startSubsystemElement(KeycloakSamlExtension.NAMESPACE, false);
+ writeSecureDeployment(writer, context.getModelNode());
+ writer.writeEndElement();
+ }
+
+ public void writeSecureDeployment(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.get(Constants.Model.SECURE_DEPLOYMENT).isDefined()) {
+ return;
+ }
+
+ for (Property sp : model.get(Constants.Model.SECURE_DEPLOYMENT).asPropertyList()) {
+ writer.writeStartElement(Constants.XML.SECURE_DEPLOYMENT);
+ writer.writeAttribute(Constants.XML.NAME, sp.getName());
+
+ writeSps(writer, sp.getValue());
+ writer.writeEndElement();
+ }
+ }
+
+ void writeSps(final XMLExtendedStreamWriter writer, final ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ for (Property sp : model.get(Constants.Model.SERVICE_PROVIDER).asPropertyList()) {
+ writer.writeStartElement(Constants.XML.SERVICE_PROVIDER);
+ writer.writeAttribute(Constants.XML.ENTITY_ID, sp.getName());
+ ModelNode spAttributes = sp.getValue();
+ for (SimpleAttributeDefinition attr : ServiceProviderDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, spAttributes, false, writer);
+ }
+ writeKeys(writer, spAttributes.get(Constants.Model.KEY));
+ writePrincipalNameMapping(writer, spAttributes);
+ writeRoleIdentifiers(writer, spAttributes);
+ writeIdentityProvider(writer, spAttributes.get(Constants.Model.IDENTITY_PROVIDER));
+
+ writer.writeEndElement();
+ }
+ }
+
+ void writeIdentityProvider(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+
+ for (Property idp : model.asPropertyList()) {
+ writer.writeStartElement(Constants.XML.IDENTITY_PROVIDER);
+ writer.writeAttribute(Constants.XML.ENTITY_ID, idp.getName());
+
+ ModelNode idpAttributes = idp.getValue();
+ for (SimpleAttributeDefinition attr : IdentityProviderDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, idpAttributes, false, writer);
+ }
+
+ writeSingleSignOn(writer, idpAttributes.get(Constants.Model.SINGLE_SIGN_ON));
+ writeSingleLogout(writer, idpAttributes.get(Constants.Model.SINGLE_LOGOUT));
+ writeKeys(writer, idpAttributes.get(Constants.Model.KEY));
+ }
+ writer.writeEndElement();
+ }
+
+ void writeSingleSignOn(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.SINGLE_SIGN_ON);
+ for (SimpleAttributeDefinition attr : SingleSignOnDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ void writeSingleLogout(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.SINGLE_LOGOUT);
+ for (SimpleAttributeDefinition attr : SingleLogoutDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ void writeKeys(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ boolean contains = false;
+ for (Property key : model.asPropertyList()) {
+ if (!contains) {
+ writer.writeStartElement(Constants.XML.KEYS);
+ contains = true;
+ }
+ writer.writeStartElement(Constants.XML.KEY);
+
+ ModelNode keyAttributes = key.getValue();
+ for (SimpleAttributeDefinition attr : KeyDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, keyAttributes, false, writer);
+ }
+ for (SimpleAttributeDefinition attr : KeyDefinition.ELEMENTS) {
+ attr.getAttributeMarshaller().marshallAsElement(attr, keyAttributes, false, writer);
+ }
+ writeKeyStore(writer, keyAttributes.get(Constants.Model.KEY_STORE));
+
+ writer.writeEndElement();
+ }
+ if (contains) {
+ writer.writeEndElement();
+ }
+ }
+
+ void writeKeyStore(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ if (!model.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.KEY_STORE);
+ for (SimpleAttributeDefinition attr : KeyStoreDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writePrivateKey(writer, model);
+ writeCertificate(writer, model);
+ writer.writeEndElement();
+ }
+
+ void writeCertificate(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode value = model.get(Constants.Model.CERTIFICATE_ALIAS);
+ if (!value.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.CERTIFICATE);
+ SimpleAttributeDefinition attr = KeyStoreCertificateDefinition.CERTIFICATE_ALIAS;
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ writer.writeEndElement();
+ }
+
+ void writePrivateKey(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode pk_alias = model.get(Constants.Model.PRIVATE_KEY_ALIAS);
+ ModelNode pk_password = model.get(Constants.Model.PRIVATE_KEY_PASSWORD);
+
+ if (!pk_alias.isDefined() && !pk_password.isDefined()) {
+ return;
+ }
+ writer.writeStartElement(Constants.XML.PRIVATE_KEY);
+ for (SimpleAttributeDefinition attr : KeyStorePrivateKeyDefinition.ATTRIBUTES) {
+ attr.getAttributeMarshaller().marshallAsAttribute(attr, model, false, writer);
+ }
+ writer.writeEndElement();
+ }
+
+ void writeRoleIdentifiers(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ ModelNode value = model.get(Constants.Model.ROLE_ATTRIBUTES);
+ if (!value.isDefined()) {
+ return;
+ }
+
+ List<ModelNode> items = value.asList();
+ if (items.size() == 0) {
+ return;
+ }
+
+ writer.writeStartElement(Constants.XML.ROLE_IDENTIFIERS);
+ for (ModelNode item : items) {
+ writer.writeStartElement(Constants.XML.ATTRIBUTE);
+ writer.writeAttribute("name", item.asString());
+ writer.writeEndElement();
+ }
+ writer.writeEndElement();
+ }
+
+ void writePrincipalNameMapping(XMLExtendedStreamWriter writer, ModelNode model) throws XMLStreamException {
+ writer.writeStartElement(Constants.XML.PRINCIPAL_NAME_MAPPING);
+ ModelNode value = model.get(Constants.Model.PRINCIPAL_NAME_MAPPING_POLICY);
+ if (value.isDefined()) {
+ writer.writeAttribute(Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY, value.asString());
+ }
+ value = model.get(Constants.Model.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME);
+ if (value.isDefined()) {
+ writer.writeAttribute(Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, value.asString());
+ }
+ writer.writeEndElement();
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java
new file mode 100644
index 0000000..7d19994
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ObjectTypeAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SIGNING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGNING, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGNING)
+ .build();
+
+ static final SimpleAttributeDefinition ENCRYPTION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.ENCRYPTION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.ENCRYPTION)
+ .build();
+
+ static final SimpleAttributeDefinition PRIVATE_KEY_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_PEM)
+ .build();
+
+ static final SimpleAttributeDefinition PUBLIC_KEY_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PUBLIC_KEY_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PUBLIC_KEY_PEM)
+ .build();
+
+ static final SimpleAttributeDefinition CERTIFICATE_PEM =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.CERTIFICATE_PEM, ModelType.STRING, true)
+ .setXmlName(Constants.XML.CERTIFICATE_PEM)
+ .build();
+
+ static final ObjectTypeAttributeDefinition KEY_STORE =
+ ObjectTypeAttributeDefinition.Builder.of(Constants.Model.KEY_STORE,
+ KeyStoreDefinition.ALL_ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGNING, ENCRYPTION};
+ static final SimpleAttributeDefinition[] ELEMENTS = {PRIVATE_KEY_PEM, PUBLIC_KEY_PEM, CERTIFICATE_PEM};
+ static final AttributeDefinition[] ALL_ATTRIBUTES = {SIGNING, ENCRYPTION, PRIVATE_KEY_PEM, PUBLIC_KEY_PEM, CERTIFICATE_PEM, KEY_STORE};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final HashMap<String, SimpleAttributeDefinition> ELEMENT_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ELEMENTS) {
+ ELEMENT_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static final KeyDefinition INSTANCE = new KeyDefinition();
+
+ private KeyDefinition() {
+ super(PathElement.pathElement(Constants.Model.KEY),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.KEY),
+ new KeyAddHandler(),
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+
+ static SimpleAttributeDefinition lookupElement(String xmlName) {
+ return ELEMENT_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java
new file mode 100644
index 0000000..83dc057
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyStoreCertificateDefinition {
+
+ static final SimpleAttributeDefinition CERTIFICATE_ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.CERTIFICATE_ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.CERTIFICATE_ALIAS)
+ .build();
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return Constants.XML.CERTIFICATE_ALIAS.equals(xmlName) ? CERTIFICATE_ALIAS : null;
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java
new file mode 100644
index 0000000..a71e89a
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class KeyStoreDefinition {
+
+ static final SimpleAttributeDefinition RESOURCE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESOURCE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESOURCE)
+ .build();
+
+ static final SimpleAttributeDefinition PASSWORD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PASSWORD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PASSWORD)
+ .build();
+
+ static final SimpleAttributeDefinition FILE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.FILE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.FILE)
+ .build();
+
+ static final SimpleAttributeDefinition TYPE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.TYPE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.TYPE)
+ .build();
+
+ static final SimpleAttributeDefinition ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.ALIAS)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {RESOURCE, PASSWORD, FILE, TYPE, ALIAS};
+ static final SimpleAttributeDefinition[] ALL_ATTRIBUTES = {RESOURCE, PASSWORD, FILE, TYPE, ALIAS,
+ KeyStorePrivateKeyDefinition.PRIVATE_KEY_ALIAS,
+ KeyStorePrivateKeyDefinition.PRIVATE_KEY_PASSWORD,
+ KeyStoreCertificateDefinition.CERTIFICATE_ALIAS
+ };
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java
new file mode 100644
index 0000000..bbb398d
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class KeyStorePrivateKeyDefinition {
+ static final SimpleAttributeDefinition PRIVATE_KEY_ALIAS =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_ALIAS, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_ALIAS)
+ .build();
+
+ static final SimpleAttributeDefinition PRIVATE_KEY_PASSWORD =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRIVATE_KEY_PASSWORD, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRIVATE_KEY_PASSWORD)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {PRIVATE_KEY_ALIAS, PRIVATE_KEY_PASSWORD};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java
new file mode 100755
index 0000000..d068b64
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java
@@ -0,0 +1,45 @@
+/*
+ * 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.adapter.saml.extension.logging;
+
+import org.jboss.logging.BasicLogger;
+import org.jboss.logging.Logger;
+import org.jboss.logging.annotations.LogMessage;
+import org.jboss.logging.annotations.Message;
+import org.jboss.logging.annotations.MessageLogger;
+
+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);
+
+
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java
new file mode 100755
index 0000000..be44809
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/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.adapter.saml.extension.logging;
+
+import org.jboss.logging.Messages;
+import org.jboss.logging.annotations.MessageBundle;
+
+/**
+ * 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 = "KEYCLOAK")
+public interface KeycloakMessages {
+
+ /**
+ * The messages
+ */
+ KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class);
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java
new file mode 100644
index 0000000..22de93d
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class SecureDeploymentAddHandler extends AbstractAddStepHandler {
+
+ static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
+
+ private SecureDeploymentAddHandler() {
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java
new file mode 100644
index 0000000..bf6cab5
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.*;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Defines attributes and operations for a secure-deployment.
+ */
+public class SecureDeploymentDefinition extends SimpleResourceDefinition {
+
+ static final SecureDeploymentDefinition INSTANCE = new SecureDeploymentDefinition();
+
+ private SecureDeploymentDefinition() {
+ super(PathElement.pathElement(Constants.Model.SECURE_DEPLOYMENT),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.SECURE_DEPLOYMENT),
+ SecureDeploymentAddHandler.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java
new file mode 100644
index 0000000..f9f3070
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+class ServiceProviderAddHandler extends AbstractAddStepHandler {
+
+ static final ServiceProviderAddHandler INSTANCE = new ServiceProviderAddHandler();
+
+ ServiceProviderAddHandler() {
+ super(ServiceProviderDefinition.ALL_ATTRIBUTES);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ Configuration.INSTANCE.updateModel(operation, model);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
new file mode 100644
index 0000000..cb84f12
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.ListAttributeDefinition;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.StringListAttributeDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ServiceProviderDefinition extends SimpleResourceDefinition {
+
+ static final SimpleAttributeDefinition SSL_POLICY =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SSL_POLICY, ModelType.STRING, true)
+ .setXmlName(Constants.XML.SSL_POLICY)
+ .build();
+
+ static final SimpleAttributeDefinition NAME_ID_POLICY_FORMAT =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.NAME_ID_POLICY_FORMAT, ModelType.STRING, true)
+ .setXmlName(Constants.XML.NAME_ID_POLICY_FORMAT)
+ .build();
+
+ static final SimpleAttributeDefinition LOGOUT_PAGE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.LOGOUT_PAGE, ModelType.STRING, true)
+ .setXmlName(Constants.XML.LOGOUT_PAGE)
+ .build();
+
+ static final SimpleAttributeDefinition FORCE_AUTHENTICATION =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.FORCE_AUTHENTICATION, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.FORCE_AUTHENTICATION)
+ .build();
+
+ static final SimpleAttributeDefinition PRINCIPAL_NAME_MAPPING_POLICY =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRINCIPAL_NAME_MAPPING_POLICY, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRINCIPAL_NAME_MAPPING_POLICY)
+ .build();
+
+ static final SimpleAttributeDefinition PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ModelType.STRING, true)
+ .setXmlName(Constants.XML.PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME)
+ .build();
+
+ static final ListAttributeDefinition ROLE_ATTRIBUTES =
+ new StringListAttributeDefinition.Builder(Constants.Model.ROLE_ATTRIBUTES)
+ .setAllowNull(false)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SSL_POLICY, NAME_ID_POLICY_FORMAT, LOGOUT_PAGE, FORCE_AUTHENTICATION};
+ static final AttributeDefinition[] ELEMENTS = {PRINCIPAL_NAME_MAPPING_POLICY, PRINCIPAL_NAME_MAPPING_ATTRIBUTE_NAME, ROLE_ATTRIBUTES};
+
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+ static final HashMap<String, AttributeDefinition> ALL_MAP = new HashMap<>();
+ static final Collection<AttributeDefinition> ALL_ATTRIBUTES;
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+
+ ALL_MAP.putAll(ATTRIBUTE_MAP);
+ for (AttributeDefinition def : ELEMENTS) {
+ ALL_MAP.put(def.getXmlName(), def);
+ }
+ ALL_ATTRIBUTES = Collections.unmodifiableCollection(ALL_MAP.values());
+ }
+
+ static final ServiceProviderDefinition INSTANCE = new ServiceProviderDefinition();
+
+ private ServiceProviderDefinition() {
+ super(PathElement.pathElement(Constants.Model.SERVICE_PROVIDER),
+ KeycloakSamlExtension.getResourceDescriptionResolver(Constants.Model.SERVICE_PROVIDER),
+ ServiceProviderAddHandler.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ final OperationStepHandler writeHandler = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES);
+ for (AttributeDefinition attribute : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attribute, null, writeHandler);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java
new file mode 100644
index 0000000..6ad99b1
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class SingleLogoutDefinition {
+
+ static final SimpleAttributeDefinition VALIDATE_REQUEST_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_REQUEST_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_REQUEST_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition VALIDATE_RESPONSE_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_RESPONSE_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition SIGN_REQUEST =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_REQUEST, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_REQUEST)
+ .build();
+
+ static final SimpleAttributeDefinition SIGN_RESPONSE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_RESPONSE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_RESPONSE)
+ .build();
+
+ static final SimpleAttributeDefinition REQUEST_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REQUEST_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition RESPONSE_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESPONSE_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESPONSE_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition POST_BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.POST_BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.POST_BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition REDIRECT_BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REDIRECT_BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REDIRECT_BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {VALIDATE_REQUEST_SIGNATURE, VALIDATE_RESPONSE_SIGNATURE,
+ SIGN_REQUEST, SIGN_RESPONSE, REQUEST_BINDING, RESPONSE_BINDING, POST_BINDING_URL, REDIRECT_BINDING_URL};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java
new file mode 100644
index 0000000..fe78c02
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 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.adapter.saml.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.dmr.ModelType;
+
+import java.util.HashMap;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+abstract class SingleSignOnDefinition {
+
+ static final SimpleAttributeDefinition SIGN_REQUEST =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.SIGN_REQUEST, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.SIGN_REQUEST)
+ .build();
+
+ static final SimpleAttributeDefinition VALIDATE_RESPONSE_SIGNATURE =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.VALIDATE_RESPONSE_SIGNATURE, ModelType.BOOLEAN, true)
+ .setXmlName(Constants.XML.VALIDATE_RESPONSE_SIGNATURE)
+ .build();
+
+ static final SimpleAttributeDefinition REQUEST_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.REQUEST_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.REQUEST_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition RESPONSE_BINDING =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.RESPONSE_BINDING, ModelType.STRING, true)
+ .setXmlName(Constants.XML.RESPONSE_BINDING)
+ .build();
+
+ static final SimpleAttributeDefinition BINDING_URL =
+ new SimpleAttributeDefinitionBuilder(Constants.Model.BINDING_URL, ModelType.STRING, true)
+ .setXmlName(Constants.XML.BINDING_URL)
+ .build();
+
+ static final SimpleAttributeDefinition[] ATTRIBUTES = {SIGN_REQUEST, VALIDATE_RESPONSE_SIGNATURE, REQUEST_BINDING, RESPONSE_BINDING, BINDING_URL};
+
+ static final HashMap<String, SimpleAttributeDefinition> ATTRIBUTE_MAP = new HashMap<>();
+
+ static {
+ for (SimpleAttributeDefinition def : ATTRIBUTES) {
+ ATTRIBUTE_MAP.put(def.getXmlName(), def);
+ }
+ }
+
+ static SimpleAttributeDefinition lookup(String xmlName) {
+ return ATTRIBUTE_MAP.get(xmlName);
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
new file mode 100755
index 0000000..f8a4a11
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
@@ -0,0 +1,63 @@
+keycloak-saml.subsystem=Keycloak adapter subsystem
+keycloak-saml.subsystem.add=Operation Adds Keycloak adapter subsystem
+keycloak-saml.subsystem.remove=Operation removes Keycloak adapter subsystem
+keycloak-saml.subsystem.secure-deployment=A deployment secured by Keycloak.
+
+keycloak-saml.secure-deployment=A deployment secured by Keycloak
+keycloak-saml.secure-deployment.add=Add a deployment to be secured by Keycloak
+keycloak-saml.secure-deployment.remove=Remove a deployment to be secured by Keycloak
+keycloak-saml.secure-deployment.service-provider=A security provider configuration for secure deployment
+
+keycloak-saml.service-provider=A security provider configuration for secure deployment
+keycloak-saml.service-provider.add=Add a security provider configuration to deployment secured by Keycloak SAML
+keycloak-saml.service-provider.remove=Remove a security provider definition from deployment secured by Keycloak SAML
+keycloak-saml.service-provider.ssl-policy=SSL Policy to use
+keycloak-saml.service-provider.name-id-policy-format=Name ID policy format URN
+keycloak-saml.service-provider.logout-page=URI to a logout page
+keycloak-saml.service-provider.force-authentication=Redirected unauthenticated request to a login page
+keycloak-saml.service-provider.role-attributes=Role identifiers
+keycloak-saml.service-provider.principal-name-mapping-policy=Principal name mapping policy
+keycloak-saml.service-provider.principal-name-mapping-attribute-name=Principal name mapping attribute name
+keycloak-saml.service-provider.key=A key definition
+keycloak-saml.service-provider.identity-provider=Identity provider definition
+
+keycloak-saml.key=A key configuration for service provider or identity provider
+keycloak-saml.key.add=Add a key definition
+keycloak-saml.key.remove=Remove a key definition
+keycloak-saml.key.signing=Key can be used for signing
+keycloak-saml.key.encryption=Key can be used for encryption
+keycloak-saml.key.private-key-pem=Private key string in pem format
+keycloak-saml.key.public-key-pem=Public key string in pem format
+keycloak-saml.key.certificate-pem=Certificate key string in pem format
+keycloak-saml.key.key-store=Key store definition
+keycloak-saml.key.key-store.file=Key store filesystem path
+keycloak-saml.key.key-store.resource=Key store resource URI
+keycloak-saml.key.key-store.password=Key store password
+keycloak-saml.key.key-store.type=Key store format
+keycloak-saml.key.key-store.alias=Key alias
+keycloak-saml.key.key-store.private-key-alias=Private key alias
+keycloak-saml.key.key-store.private-key-password=Private key password
+keycloak-saml.key.key-store.certificate-alias=Certificate alias
+
+keycloak-saml.identity-provider=An identity provider configuration
+keycloak-saml.identity-provider.add=Add an identity provider
+keycloak-saml.identity-provider.remove=Remove an identity provider
+keycloak-saml.identity-provider.signatures-required=Require signatures for single-sign-on and single-logout
+keycloak-saml.identity-provider.signature-algorithm=Signature algorithm
+keycloak-saml.identity-provider.signature-canonicalization-method=Signature canonicalization method
+keycloak-saml.identity-provider.single-sign-on=Single sign-on configuration
+keycloak-saml.identity-provider.single-sign-on.sign-request=Sign SSO requests
+keycloak-saml.identity-provider.single-sign-on.validate-response-signature=Validate an SSO response signature
+keycloak-saml.identity-provider.single-sign-on.request-binding=HTTP method to use for requests
+keycloak-saml.identity-provider.single-sign-on.response-binding=HTTP method to use for responses
+keycloak-saml.identity-provider.single-sign-on.binding-url=SSO endpoint URL
+keycloak-saml.identity-provider.single-logout=Single logout configuration
+keycloak-saml.identity-provider.single-logout.validate-request-signature=Validate a single-logout request signature
+keycloak-saml.identity-provider.single-logout.validate-response-signature=Validate a single-logout response signature
+keycloak-saml.identity-provider.single-logout.sign-request=Sign single-logout requests
+keycloak-saml.identity-provider.single-logout.sign-response=Sign single-logout responses
+keycloak-saml.identity-provider.single-logout.request-binding=HTTP method to use for request
+keycloak-saml.identity-provider.single-logout.response-binding=HTTP method to use for response
+keycloak-saml.identity-provider.single-logout.post-binding-url=Endpoint URL for posting
+keycloak-saml.identity-provider.single-logout.redirect-binding-url=Endpoint URL for redirects
+keycloak-saml.identity-provider.key=Key definition for identity provider
\ No newline at end of file
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
new file mode 100755
index 0000000..725104b
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="urn:jboss:domain:keycloak-saml:1.1"
+ xmlns="urn:jboss:domain:keycloak-saml:1.1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1.0">
+
+ <!-- The subsystem root element -->
+ <xs:element name="subsystem" type="subsystem-type"/>
+
+ <xs:complexType name="subsystem-type">
+ <xs:annotation>
+ <xs:documentation>
+ <![CDATA[
+ The Keycloak SAML adapter subsystem, used to register deployments managed by Keycloak SAML adapter
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:all>
+ <xs:element name="secure-deployment" minOccurs="0" type="secure-deployment-type"/>
+ </xs:all>
+ </xs:complexType>
+
+ <xs:complexType name="secure-deployment-type">
+ <xs:all>
+ <xs:element name="SP" minOccurs="1" maxOccurs="1" type="sp-type"/>
+ </xs:all>
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The name of the realm.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="sp-type">
+ <xs:all>
+ <xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
+ <xs:element name="PrincipalNameMapping" minOccurs="0" maxOccurs="1" type="principal-name-mapping-type"/>
+ <xs:element name="RoleIdentifiers" minOccurs="0" maxOccurs="1" type="role-identifiers-type"/>
+ <xs:element name="IDP" minOccurs="1" maxOccurs="1" type="identity-provider-type"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The entity ID for SAML service provider</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="sslPolicy" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The ssl policy</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Name ID policy format URN</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="logoutPage" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>URI to a logout page</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="forceAuthentication" type="xs:boolean" use="required">
+ <xs:annotation>
+ <xs:documentation>Redirected unauthenticated request to a login page</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="identity-provider-type">
+ <xs:all minOccurs="1" maxOccurs="1">
+ <xs:element name="SingleSignOnService" minOccurs="1" maxOccurs="1" type="single-signon-type"/>
+ <xs:element name="SingleLogoutService" minOccurs="0" maxOccurs="1" type="single-logout-type"/>
+ <xs:element name="Keys" minOccurs="0" maxOccurs="1" type="keys-type"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The entity ID for SAML service provider</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signaturesRequired" type="xs:boolean" use="required">
+ <xs:annotation>
+ <xs:documentation>Require signatures for single-sign-on and single-logout</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureAlgorithm" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Algorithm used for signatures</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Canonicalization method used for signatures</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="single-signon-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign the SSO requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate the SSO response signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for response</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="bindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>SSO endpoint URL</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="single-logout-type">
+ <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate a single-logout request signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Validate a single-logout response signature</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign single-logout requests</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="signResponse" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Sign single-logout responses</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for request</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>HTTP method to use for response</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="postBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Endpoint URL for posting</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Endpoint URL for redirects</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="keys-type">
+ <xs:sequence>
+ <xs:element name="Key" minOccurs="1" maxOccurs="2" type="key-type"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="key-type">
+ <xs:all>
+ <xs:element name="KeyStore" minOccurs="0" maxOccurs="1" type="keystore-type"/>
+ <xs:element name="PrivateKeyPem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ <xs:element name="PublicKeyPem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ <xs:element name="CertificatePem" minOccurs="0" maxOccurs="1" type="xs:string"/>
+ </xs:all>
+ <xs:attribute name="signing" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key can be used for signing</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key can be used for encryption</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ <xs:complexType name="keystore-type">
+ <xs:sequence minOccurs="0" maxOccurs="1">
+ <xs:element name="PrivateKey" minOccurs="0" maxOccurs="1" type="privatekey-type"/>
+ <xs:element name="Certificate" minOccurs="0" maxOccurs="1" type="certificate-type"/>
+ </xs:sequence>
+ <xs:attribute name="file" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store filesystem path</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="resource" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store resource URI</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Key store password</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="type" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key store format</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="alias" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Key alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="privatekey-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Private key alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="password" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Private key password</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="certificate-type">
+ <xs:attribute name="alias" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Certificate alias</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="principal-name-mapping-type">
+ <xs:attribute name="policy" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Principal name mapping policy. Possible values: FROM_NAME_ID</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ <xs:attribute name="attribute" type="xs:string" use="optional">
+ <xs:annotation>
+ <xs:documentation>Name of the attribute to use for principal name mapping</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="role-identifiers-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Attribute" minOccurs="0" maxOccurs="unbounded" type="attribute-type"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:complexType name="attribute-type">
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>Role attribute</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+</xs:schema>
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java
new file mode 100755
index 0000000..fdd8ee2
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java
@@ -0,0 +1,56 @@
+/*
+ * 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.adapter.saml.extension;
+
+import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
+
+import java.io.IOException;
+
+
+/**
+ * Tests all management expects for subsystem, parsing, marshaling, model definition and other
+ * Here is an example that allows you a fine grained controller over what is tested and how. So it can give you ideas what can be done and tested.
+ * If you have no need for advanced testing of subsystem you look at {@link SubsystemBaseParsingTestCase} that testes same stuff but most of the code
+ * is hidden inside of test harness
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @author Tomaz Cerar
+ * @author <a href="marko.strukelj@gmail.com">Marko Strukelj</a>
+ */
+public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
+
+ public SubsystemParsingTestCase() {
+ super(KeycloakSamlExtension.SUBSYSTEM_NAME, new KeycloakSamlExtension());
+ }
+
+ @Override
+ protected String getSubsystemXml() throws IOException {
+ return readResource("keycloak-saml-1.1.xml");
+ }
+
+ @Override
+ protected String getSubsystemXsdPath() throws Exception {
+ return "schema/wildfly-keycloak-saml_1_1.xsd";
+ }
+
+ @Override
+ protected String[] getSubsystemTemplatePaths() throws IOException {
+ return new String[]{
+ "/subsystem-templates/keycloak-saml-adapter.xml"
+ };
+ }
+}
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
new file mode 100644
index 0000000..6f56fb0
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml
@@ -0,0 +1,50 @@
+<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ <secure-deployment name="my-app.war">
+ <SP entityID="http://localhost:8080/sales-post-enc/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+
+ <Keys>
+ <Key encryption="true" signing="true">
+ <PrivateKeyPem>my_key.pem</PrivateKeyPem>
+ <PublicKeyPem>my_key.pub</PublicKeyPem>
+ <CertificatePem>cert.cer</CertificatePem>
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123" file="test" alias="test" type="jks">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID" attribute="test"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ <Attribute name="Role2"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp" signaturesRequired="true" signatureAlgorithm="test" signatureCanonicalizationMethod="test">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <Keys>
+ <Key signing="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="saml-demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
diff --git a/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml
new file mode 100644
index 0000000..26d0e98
--- /dev/null
+++ b/saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml
@@ -0,0 +1,50 @@
+<subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ <secure-deployment name="my-app.war">
+ <SP entityID="http://localhost:8080/sales-post-enc/"
+ sslPolicy="EXTERNAL"
+ nameIDPolicyFormat="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
+ logoutPage="/logout.jsp"
+ forceAuthentication="false">
+
+ <Keys>
+ <Key encryption="true" signing="true">
+ <PrivateKeyPem>my_key.pem</PrivateKeyPem>
+ <PublicKeyPem>my_key.pub</PublicKeyPem>
+ <CertificatePem>cert.cer</CertificatePem>
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123" file="test" alias="test" type="jks">
+ <PrivateKey alias="http://localhost:8080/sales-post-enc/" password="test123"/>
+ <Certificate alias="http://localhost:8080/sales-post-enc/"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="FROM_NAME_ID" attribute="test"/>
+ <RoleIdentifiers>
+ <Attribute name="Role"/>
+ <Attribute name="Role2"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp" signaturesRequired="true" signatureAlgorithm="test" signatureCanonicalizationMethod="test" encryption="test">
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ bindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="POST"
+ responseBinding="POST"
+ postBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"
+ redirectBindingUrl="http://localhost:8080/auth/realms/saml-demo/protocol/saml"/>
+ <Keys>
+ <Key signing="true">
+ <KeyStore resource="/WEB-INF/keystore.jks" password="store123">
+ <Certificate alias="saml-demo"/>
+ </KeyStore>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java
index a6a295c..9974469 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java
@@ -1,9 +1,9 @@
package org.keycloak.protocol.saml.mappers;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ProtocolMapperModel;
-import org.keycloak.models.RoleContainerModel;
-import org.keycloak.models.RoleModel;
+import org.keycloak.Config;
+import org.keycloak.models.*;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.protocol.oidc.mappers.AbstractOIDCProtocolMapper;
import org.keycloak.protocol.saml.SamlProtocol;
@@ -19,7 +19,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
-public class RoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRoleNameMapper {
+public class RoleNameMapper implements SAMLRoleNameMapper, ProtocolMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
@@ -49,23 +49,19 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRo
return configProperties;
}
- @Override
public String getId() {
return PROVIDER_ID;
}
- @Override
public String getDisplayType() {
return "Role Name Mapper";
}
- @Override
public String getDisplayCategory() {
return "Role Mapper";
}
- @Override
public String getHelpText() {
return "Map an assigned role to a new name";
}
@@ -109,4 +105,26 @@ public class RoleNameMapper extends AbstractOIDCProtocolMapper implements SAMLRo
}
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public final ProtocolMapper create(KeycloakSession session) {
+ throw new RuntimeException("UNSUPPORTED METHOD");
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
}
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index ff1275a..3402593 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -142,15 +142,15 @@ public class SamlService extends AuthorizationEndpointBase {
event.error(Errors.CLIENT_DISABLED);
return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
}
- if ((client instanceof ClientModel) && ((ClientModel) client).isBearerOnly()) {
+ if (client.isBearerOnly()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
return ErrorPage.error(session, Messages.BEARER_ONLY);
}
- if (client.isDirectGrantsOnly()) {
+ if (!client.isStandardFlowEnabled()) {
event.event(EventType.LOGIN);
event.error(Errors.NOT_ALLOWED);
- return ErrorPage.error(session, Messages.DIRECT_GRANTS_ONLY);
+ return ErrorPage.error(session, Messages.STANDARD_FLOW_DISABLED);
}
session.getContext().setClient(client);
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index dafaf1a..710c77d 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -470,7 +470,8 @@ public class AuthenticationProcessor {
LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
protocol.setRealm(getRealm())
.setHttpHeaders(getHttpRequest().getHttpHeaders())
- .setUriInfo(getUriInfo());
+ .setUriInfo(getUriInfo())
+ .setEventBuilder(event);
Response response = protocol.sendError(getClientSession(), Error.CANCELLED_BY_USER);
forceChallenge(response);
}
@@ -754,7 +755,7 @@ public class AuthenticationProcessor {
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
}
- if (!code.isActionActive(action)) {
+ if (!code.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
throw new AuthenticationFlowException(AuthenticationFlowError.EXPIRED_CODE);
}
clientSession.setTimestamp(Time.currentTime());
@@ -808,7 +809,7 @@ public class AuthenticationProcessor {
public Response finishAuthentication() {
event.success();
RealmModel realm = clientSession.getRealm();
- return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection);
+ return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, connection, event);
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index c8d5656..9ed45de 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -1,19 +1,11 @@
package org.keycloak.protocol.oidc.endpoints;
-import java.util.List;
-
import javax.ws.rs.GET;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.common.ClientConnection;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -24,12 +16,12 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.AuthorizationEndpointBase;
-import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.Urls;
@@ -55,11 +47,13 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private ClientSessionModel clientSession;
private Action action;
+ private OIDCResponseType parsedResponseType;
private String clientId;
private String redirectUri;
private String redirectUriParam;
private String responseType;
+ private String responseMode;
private String state;
private String scope;
private String loginHint;
@@ -80,6 +74,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM);
responseType = params.getFirst(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ responseMode = params.getFirst(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
redirectUriParam = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM);
state = params.getFirst(OIDCLoginProtocol.STATE_PARAM);
scope = params.getFirst(OIDCLoginProtocol.SCOPE_PARAM);
@@ -90,8 +85,8 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
checkSsl();
checkRealm();
- checkClient();
checkResponseType();
+ checkClient();
checkRedirectUri();
createClientSession();
@@ -172,9 +167,14 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
throw new ErrorPageException(session, Messages.BEARER_ONLY);
}
- if (client.isDirectGrantsOnly()) {
+ if ((parsedResponseType.hasResponseType(OIDCResponseType.CODE) || parsedResponseType.hasResponseType(OIDCResponseType.NONE)) && !client.isStandardFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
- throw new ErrorPageException(session, Messages.DIRECT_GRANTS_ONLY);
+ throw new ErrorPageException(session, Messages.STANDARD_FLOW_DISABLED);
+ }
+
+ if (parsedResponseType.isImplicitOrHybridFlow() && !client.isImplicitFlowEnabled()) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new ErrorPageException(session, Messages.IMPLICIT_FLOW_DISABLED);
}
session.getContext().setClient(client);
@@ -192,14 +192,32 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
event.detail(Details.RESPONSE_TYPE, responseType);
- if (responseType.equals(OAuth2Constants.CODE)) {
+ try {
+ parsedResponseType = OIDCResponseType.parse(responseType);
if (action == null) {
action = Action.CODE;
}
- } else {
+ } catch (IllegalArgumentException iae) {
+ logger.error(iae.getMessage());
event.error(Errors.INVALID_REQUEST);
throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
}
+
+ try {
+ OIDCResponseMode parsedResponseMode = OIDCResponseMode.parse(responseMode, parsedResponseType);
+ event.detail(Details.RESPONSE_MODE, parsedResponseMode.toString().toLowerCase());
+
+ // Disallowed by OIDC specs
+ if (parsedResponseType.isImplicitOrHybridFlow() && parsedResponseMode == OIDCResponseMode.QUERY) {
+ logger.error("Response_mode 'query' not allowed for implicit or hybrid flow");
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ }
+
+ } catch (IllegalArgumentException iae) {
+ event.error(Errors.INVALID_REQUEST);
+ throw new ErrorPageException(session, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ }
}
private void checkRedirectUri() {
@@ -228,6 +246,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
if (loginHint != null) clientSession.setNote(OIDCLoginProtocol.LOGIN_HINT_PARAM, loginHint);
if (prompt != null) clientSession.setNote(OIDCLoginProtocol.PROMPT_PARAM, prompt);
if (idpHint != null) clientSession.setNote(AdapterConstants.KC_IDP_HINT, idpHint);
+ if (responseMode != null) clientSession.setNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM, responseMode);
}
private Response buildAuthorizationCodeAuthorizationResponse() {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index 34161d8..fe358e0 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -202,7 +202,7 @@ public class TokenEndpoint {
ClientSessionModel clientSession = accessCode.getClientSession();
event.detail(Details.CODE_ID, clientSession.getId());
- if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name())) {
+ if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException("invalid_grant", "Code is expired", Response.Status.BAD_REQUEST);
}
@@ -327,7 +327,7 @@ public class TokenEndpoint {
}
public Response buildResourceOwnerPasswordCredentialsGrant() {
- event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
+ event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
if (client.isConsentRequired()) {
event.error(Errors.CONSENT_DENIED);
@@ -393,7 +393,7 @@ public class TokenEndpoint {
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
}
- event.detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH);
+ event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index b9d55db..40bcc67 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -33,6 +33,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.protocol.oidc.utils.OIDCRedirectUriBuilder;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.ResourceAdminManager;
@@ -62,6 +66,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
public static final String LOGOUT_REDIRECT_URI = "OIDC_LOGOUT_REDIRECT_URI";
public static final String ISSUER = "iss";
+ public static final String RESPONSE_MODE_PARAM = "response_mode";
+
private static final Logger log = Logger.getLogger(OIDCLoginProtocol.class);
protected KeycloakSession session;
@@ -74,6 +80,9 @@ public class OIDCLoginProtocol implements LoginProtocol {
protected EventBuilder event;
+ protected OIDCResponseType responseType;
+ protected OIDCResponseMode responseMode;
+
public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, EventBuilder event) {
this.session = session;
this.realm = realm;
@@ -86,6 +95,15 @@ public class OIDCLoginProtocol implements LoginProtocol {
}
+ private void setupResponseTypeAndMode(ClientSessionModel clientSession) {
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ String responseMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ this.responseType = OIDCResponseType.parse(responseType);
+ this.responseMode = OIDCResponseMode.parse(responseMode, this.responseType);
+ this.event.detail(Details.RESPONSE_TYPE, responseType);
+ this.event.detail(Details.RESPONSE_MODE, this.responseMode.toString().toLowerCase());
+ }
+
@Override
public OIDCLoginProtocol setSession(KeycloakSession session) {
this.session = session;
@@ -116,32 +134,63 @@ public class OIDCLoginProtocol implements LoginProtocol {
return this;
}
+
@Override
public Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode) {
ClientSessionModel clientSession = accessCode.getClientSession();
+ setupResponseTypeAndMode(clientSession);
+
String redirect = clientSession.getRedirectUri();
+ OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
- accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode());
log.debugv("redirectAccessCode: state: {0}", state);
if (state != null)
- redirectUri.queryParam(OAuth2Constants.STATE, state);
- Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
+ redirectUri.addParam(OAuth2Constants.STATE, state);
- return location.build();
+ // Standard or hybrid flow
+ if (responseType.hasResponseType(OIDCResponseType.CODE)) {
+ accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
+ redirectUri.addParam(OAuth2Constants.CODE, accessCode.getCode());
+ }
+
+ // Implicit or hybrid flow
+ if (responseType.isImplicitOrHybridFlow()) {
+ TokenManager tokenManager = new TokenManager();
+ AccessTokenResponse res = tokenManager.responseBuilder(realm, clientSession.getClient(), event, session, userSession, clientSession)
+ .generateAccessToken()
+ .generateIDToken()
+ .build();
+
+ if (responseType.hasResponseType(OIDCResponseType.ID_TOKEN)) {
+ redirectUri.addParam("id_token", res.getIdToken());
+ }
+
+ if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
+ redirectUri.addParam("access_token", res.getToken());
+ redirectUri.addParam("token_type", res.getTokenType());
+ redirectUri.addParam("session-state", res.getSessionState());
+ redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn()));
+ }
+
+ redirectUri.addParam("not-before-policy", String.valueOf(res.getNotBeforePolicy()));
+ }
+
+ return redirectUri.build();
}
+
@Override
public Response sendError(ClientSessionModel clientSession, Error error) {
+ setupResponseTypeAndMode(clientSession);
+
String redirect = clientSession.getRedirectUri();
String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
- UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.ERROR, translateError(error));
+ OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error));
if (state != null)
- redirectUri.queryParam(OAuth2Constants.STATE, state);
+ redirectUri.addParam(OAuth2Constants.STATE, state);
session.sessions().removeClientSession(realm, clientSession);
RestartLoginCookie.expireRestartCookie(realm, session.getContext().getConnection(), uriInfo);
- Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
- return location.build();
+ return redirectUri.build();
}
private String translateError(Error error) {
@@ -161,10 +210,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
@Override
public void backchannelLogout(UserSessionModel userSession, ClientSessionModel clientSession) {
- if (!(clientSession.getClient() instanceof ClientModel))
- return;
- ClientModel app = clientSession.getClient();
- new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, app, clientSession);
+ ClientModel client = clientSession.getClient();
+ new ResourceAdminManager(session).logoutClientSession(uriInfo.getRequestUri(), realm, client, clientSession);
}
@Override
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 32a9a6d..e76734e 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -9,6 +9,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
@@ -25,6 +26,7 @@ import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper;
import org.keycloak.protocol.oidc.mappers.OIDCIDTokenMapper;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.protocol.oidc.utils.WebOriginsUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
@@ -51,7 +53,7 @@ import java.util.Map;
import java.util.Set;
/**
- * Stateful object that creates tokens and manages oauth access codes
+ * Stateless object that creates tokens and manages oauth access codes
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -195,44 +197,46 @@ public class TokenManager {
}
public RefreshToken verifyRefreshToken(RealmModel realm, String encodedRefreshToken) throws OAuthErrorException {
- JWSInput jws = new JWSInput(encodedRefreshToken);
- RefreshToken refreshToken = null;
try {
+ JWSInput jws = new JWSInput(encodedRefreshToken);
+ RefreshToken refreshToken = null;
if (!RSAProvider.verify(jws, realm.getPublicKey())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
}
refreshToken = jws.readJsonContent(RefreshToken.class);
- } catch (Exception e) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
- }
- if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
- }
- if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
+ if (refreshToken.getExpiration() != 0 && refreshToken.isExpired()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Refresh token expired");
+ }
+
+ if (refreshToken.getIssuedAt() < realm.getNotBefore()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
+ }
+ return refreshToken;
+ } catch (JWSInputException e) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", e);
}
- return refreshToken;
}
public IDToken verifyIDToken(RealmModel realm, String encodedIDToken) throws OAuthErrorException {
- JWSInput jws = new JWSInput(encodedIDToken);
- IDToken idToken = null;
try {
+ JWSInput jws = new JWSInput(encodedIDToken);
+ IDToken idToken;
if (!RSAProvider.verify(jws, realm.getPublicKey())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken");
}
idToken = jws.readJsonContent(IDToken.class);
- } catch (IOException e) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e);
- }
- if (idToken.isExpired()) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "IDToken expired");
- }
- if (idToken.getIssuedAt() < realm.getNotBefore()) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale IDToken");
+ if (idToken.isExpired()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "IDToken expired");
+ }
+
+ if (idToken.getIssuedAt() < realm.getNotBefore()) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale IDToken");
+ }
+ return idToken;
+ } catch (JWSInputException e) {
+ throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid IDToken", e);
}
- return idToken;
}
public AccessToken createClientAccessToken(KeycloakSession session, Set<RoleModel> requestedRoles, RealmModel realm, ClientModel client, UserModel user, UserSessionModel userSession, ClientSessionModel clientSession) {
@@ -452,8 +456,9 @@ public class TokenManager {
if (session != null) {
token.setSessionState(session.getId());
}
- if (realm.getAccessTokenLifespan() > 0) {
- token.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
+ int tokenLifespan = getTokenLifespan(realm, clientSession);
+ if (tokenLifespan > 0) {
+ token.expiration(Time.currentTime() + tokenLifespan);
}
Set<String> allowedOrigins = client.getWebOrigins();
if (allowedOrigins != null) {
@@ -462,6 +467,15 @@ public class TokenManager {
return token;
}
+ private int getTokenLifespan(RealmModel realm, ClientSessionModel clientSession) {
+ boolean implicitFlow = false;
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ if (responseType != null) {
+ implicitFlow = OIDCResponseType.parse(responseType).isImplicitFlow();
+ }
+ return implicitFlow ? realm.getAccessTokenLifespanForImplicitFlow() : realm.getAccessTokenLifespan();
+ }
+
protected void addComposites(AccessToken token, RoleModel role) {
AccessToken.Access access = null;
if (role.getContainer() instanceof RealmModel) {
@@ -579,9 +593,7 @@ public class TokenManager {
idToken.issuer(accessToken.getIssuer());
idToken.setNonce(accessToken.getNonce());
idToken.setSessionState(accessToken.getSessionState());
- if (realm.getAccessTokenLifespan() > 0) {
- idToken.expiration(Time.currentTime() + realm.getAccessTokenLifespan());
- }
+ idToken.expiration(accessToken.getExpiration());
transformIDToken(session, idToken, realm, client, userSession.getUser(), userSession, clientSession);
return this;
}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
new file mode 100644
index 0000000..06cc3cc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java
@@ -0,0 +1,149 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.keycloak.common.util.Encode;
+import org.keycloak.common.util.KeycloakUriBuilder;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class OIDCRedirectUriBuilder {
+
+ protected final KeycloakUriBuilder uriBuilder;
+
+ protected OIDCRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ this.uriBuilder = uriBuilder;
+ }
+
+ public abstract OIDCRedirectUriBuilder addParam(String paramName, String paramValue);
+ public abstract Response build();
+
+
+ public static OIDCRedirectUriBuilder fromUri(String baseUri, OIDCResponseMode responseMode) {
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(baseUri);
+
+ switch (responseMode) {
+ case QUERY: return new QueryRedirectUriBuilder(uriBuilder);
+ case FRAGMENT: return new FragmentRedirectUriBuilder(uriBuilder);
+ case FORM_POST: return new FormPostRedirectUriBuilder(uriBuilder);
+ }
+
+ throw new IllegalStateException("Not possible to end here");
+ }
+
+
+ // Impl subclasses
+
+
+ // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+ public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ uriBuilder.queryParam(paramName, paramValue);
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ URI redirectUri = uriBuilder.build();
+ Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+ return location.build();
+ }
+ }
+
+
+ // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
+ public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ private StringBuilder fragment;
+
+ protected FragmentRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ String param = paramName + "=" + Encode.encodeQueryParam(paramValue);
+ if (fragment == null) {
+ fragment = new StringBuilder(param);
+ } else {
+ fragment.append("&").append(param);
+ }
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ if (fragment != null) {
+ uriBuilder.encodedFragment(fragment.toString());
+ }
+ URI redirectUri = uriBuilder.build();
+
+ Response.ResponseBuilder location = Response.status(302).location(redirectUri);
+ return location.build();
+ }
+
+ }
+
+
+ // http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
+ public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
+
+ private Map<String, String> params = new HashMap<>();
+
+ protected FormPostRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
+ super(uriBuilder);
+ }
+
+ @Override
+ public OIDCRedirectUriBuilder addParam(String paramName, String paramValue) {
+ params.put(paramName, Encode.encodeQueryParam(paramValue));
+ return this;
+ }
+
+ @Override
+ public Response build() {
+ StringBuilder builder = new StringBuilder();
+ URI redirectUri = uriBuilder.build();
+
+ builder.append("<HTML>");
+ builder.append(" <HEAD>");
+ builder.append(" <TITLE>OIDC Form_Post Response</TITLE>");
+ builder.append(" </HEAD>");
+ builder.append(" <BODY Onload=\"document.forms[0].submit()\">");
+
+ builder.append(" <FORM METHOD=\"POST\" ACTION=\"" + redirectUri.toString() + "\">");
+
+ for (Map.Entry<String, String> param : params.entrySet()) {
+ builder.append(" <INPUT TYPE=\"HIDDEN\" NAME=\"").append(param.getKey())
+ .append("\" VALUE=\"").append(param.getValue()).append("\" />");
+ }
+
+ builder.append(" <NOSCRIPT>");
+ builder.append(" <P>JavaScript is disabled. We strongly recommend to enable it. Click the button below to continue .</P>");
+ builder.append(" <INPUT name=\"continue\" TYPE=\"SUBMIT\" VALUE=\"CONTINUE\" />");
+ builder.append(" </NOSCRIPT>");
+ builder.append(" </FORM>");
+ builder.append(" </BODY>");
+ builder.append("</HTML>");
+
+ return Response.status(Response.Status.OK)
+ .type(MediaType.TEXT_HTML_TYPE)
+ .entity(builder.toString()).build();
+ }
+
+ }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
new file mode 100644
index 0000000..c255ccc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseMode.java
@@ -0,0 +1,25 @@
+package org.keycloak.protocol.oidc.utils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public enum OIDCResponseMode {
+
+ QUERY, FRAGMENT, FORM_POST;
+
+ public static OIDCResponseMode parse(String responseMode, OIDCResponseType responseType) {
+ if (responseMode == null) {
+ return getDefaultResponseMode(responseType);
+ } else {
+ return Enum.valueOf(OIDCResponseMode.class, responseMode.toUpperCase());
+ }
+ }
+
+ private static OIDCResponseMode getDefaultResponseMode(OIDCResponseType responseType) {
+ if (responseType.isImplicitOrHybridFlow()) {
+ return OIDCResponseMode.FRAGMENT;
+ } else {
+ return OIDCResponseMode.QUERY;
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
new file mode 100644
index 0000000..6377b22
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java
@@ -0,0 +1,93 @@
+package org.keycloak.protocol.oidc.utils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCResponseType {
+
+ public static final String CODE = OIDCLoginProtocol.CODE_PARAM;
+ public static final String TOKEN = "token";
+ public static final String ID_TOKEN = "id_token";
+ public static final String NONE = "none";
+
+ private static final List<String> ALLOWED_RESPONSE_TYPES = Arrays.asList(CODE, TOKEN, ID_TOKEN, NONE);
+
+ private final List<String> responseTypes;
+
+
+ private OIDCResponseType(List<String> responseTypes) {
+ this.responseTypes = responseTypes;
+ }
+
+
+ public static OIDCResponseType parse(String responseTypeParam) {
+ if (responseTypeParam == null) {
+ throw new IllegalArgumentException("response_type is null");
+ }
+
+ String[] responseTypes = responseTypeParam.trim().split(" ");
+ List<String> allowedTypes = new ArrayList<>();
+ for (String current : responseTypes) {
+ if (ALLOWED_RESPONSE_TYPES.contains(current)) {
+ allowedTypes.add(current);
+ } else {
+ throw new IllegalArgumentException("Unsupported response_type: " + responseTypeParam);
+ }
+ }
+
+ validateAllowedTypes(allowedTypes);
+
+ return new OIDCResponseType(allowedTypes);
+ }
+
+ private static void validateAllowedTypes(List<String> responseTypes) {
+ if (responseTypes.size() == 0) {
+ throw new IllegalStateException("No responseType provided");
+ }
+ if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
+ throw new IllegalArgumentException("None not allowed with some other response_type");
+ }
+ if (responseTypes.contains(ID_TOKEN) && responseTypes.size() == 1) {
+ throw new IllegalArgumentException("Not supported to use response_type=id_token alone");
+ }
+ if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
+ throw new IllegalArgumentException("Not supported to use response_type=token alone");
+ }
+ }
+
+
+ public boolean hasResponseType(String responseType) {
+ return responseTypes.contains(responseType);
+ }
+
+
+ public boolean isImplicitOrHybridFlow() {
+ return hasResponseType(TOKEN) || hasResponseType(ID_TOKEN);
+ }
+
+ public boolean isImplicitFlow() {
+ return hasResponseType(TOKEN) && hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String responseType : responseTypes) {
+ if (!first) {
+ builder.append(" ");
+ } else {
+ first = false;
+ }
+ builder.append(responseType);
+ }
+ return builder.toString();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
index 241bcff..4d54d5f 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -3,6 +3,7 @@ package org.keycloak.services.clientregistration;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.ClientModel;
@@ -44,7 +45,7 @@ public class ClientRegistrationTokenUtils {
JWSInput input;
try {
input = new JWSInput(token);
- } catch (Exception e) {
+ } catch (JWSInputException e) {
throw new ForbiddenException(e);
}
@@ -55,7 +56,7 @@ public class ClientRegistrationTokenUtils {
JsonWebToken jwt;
try {
jwt = input.readJsonContent(JsonWebToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new ForbiddenException(e);
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 0fa1b92..a868aa8 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -43,6 +43,7 @@ public class ApplianceBootstrap {
realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
realm.setSsoSessionIdleTimeout(1800);
realm.setAccessTokenLifespan(60);
+ realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
realm.setSsoSessionMaxLifespan(36000);
realm.setOfflineSessionIdleTimeout(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT);
realm.setAccessCodeLifespan(60);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index c820e4d..0131b6d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -380,7 +380,8 @@ public class AuthenticationManager {
public static Response redirectAfterSuccessfulFlow(KeycloakSession session, RealmModel realm, UserSessionModel userSession,
ClientSessionModel clientSession,
- HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection) {
+ HttpRequest request, UriInfo uriInfo, ClientConnection clientConnection,
+ EventBuilder event) {
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) {
@@ -407,7 +408,8 @@ public class AuthenticationManager {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(request.getHttpHeaders())
- .setUriInfo(uriInfo);
+ .setUriInfo(uriInfo)
+ .setEventBuilder(event);
RestartLoginCookie.expireRestartCookie(realm, clientConnection, uriInfo);
return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
@@ -429,7 +431,7 @@ public class AuthenticationManager {
}
event.success();
RealmModel realm = clientSession.getRealm();
- return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
+ return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection, event);
}
@@ -522,9 +524,11 @@ public class AuthenticationManager {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
- .setUriInfo(context.getUriInfo());
+ .setUriInfo(context.getUriInfo())
+ .setEventBuilder(event);
+ Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
- return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ return response;
}
else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
clientSession.setNote(CURRENT_REQUIRED_ACTION, model.getProviderId());
diff --git a/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
index 8107462..191f7fa 100755
--- a/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
+++ b/services/src/main/java/org/keycloak/services/managers/BruteForceProtector.java
@@ -53,12 +53,6 @@ public class BruteForceProtector implements Runnable {
}
}
- protected class SuccessfulLogin extends LoginEvent {
- public SuccessfulLogin(String realmId, String userId, String ip) {
- super(realmId, userId, ip);
- }
- }
-
protected class ShutdownEvent extends LoginEvent {
public ShutdownEvent() {
super(null, null, null);
@@ -83,7 +77,7 @@ public class BruteForceProtector implements Runnable {
logFailure(event);
UsernameLoginFailureModel user = getUserModel(session, event);
if (user == null) {
- user = session.sessions().addUserLoginFailure(realm, event.username);
+ user = session.sessions().addUserLoginFailure(realm, event.username.toLowerCase());
}
user.setLastIPFailure(event.ip);
long currentTime = System.currentTimeMillis();
@@ -122,7 +116,7 @@ public class BruteForceProtector implements Runnable {
protected UsernameLoginFailureModel getUserModel(KeycloakSession session, LoginEvent event) {
RealmModel realm = getRealmModel(session, event);
if (realm == null) return null;
- UsernameLoginFailureModel user = session.sessions().getUserLoginFailure(realm, event.username);
+ UsernameLoginFailureModel user = session.sessions().getUserLoginFailure(realm, event.username.toLowerCase());
if (user == null) return null;
return user;
}
@@ -147,7 +141,6 @@ public class BruteForceProtector implements Runnable {
}
}
-
public void run() {
final ArrayList<LoginEvent> events = new ArrayList<LoginEvent>(TRANSACTION_SIZE + 1);
try {
@@ -196,10 +189,6 @@ public class BruteForceProtector implements Runnable {
}
}
- protected void logSuccess(LoginEvent event) {
- logger.warn("login success for user " + event.username + " from ip " + event.ip);
- }
-
protected void logFailure(LoginEvent event) {
logger.warn("login failure for user " + event.username + " from ip " + event.ip);
failures++;
@@ -215,15 +204,6 @@ public class BruteForceProtector implements Runnable {
}
}
- public void successfulLogin(RealmModel realm, String username, ClientConnection clientConnection) {
- logger.info("successful login user: " + username + " from ip " + clientConnection.getRemoteAddr());
- }
-
- public void invalidUser(RealmModel realm, String username, ClientConnection clientConnection) {
- logger.warn("invalid user: " + username + " from ip " + clientConnection.getRemoteAddr());
- // todo more?
- }
-
public void failedLogin(RealmModel realm, String username, ClientConnection clientConnection) {
try {
FailedLogin event = new FailedLogin(realm.getId(), username, clientConnection.getRemoteAddr());
@@ -238,7 +218,7 @@ public class BruteForceProtector implements Runnable {
}
public boolean isTemporarilyDisabled(KeycloakSession session, RealmModel realm, String username) {
- UsernameLoginFailureModel failure = session.sessions().getUserLoginFailure(realm, username);
+ UsernameLoginFailureModel failure = session.sessions().getUserLoginFailure(realm, username.toLowerCase());
if (failure == null) {
return false;
}
@@ -251,13 +231,4 @@ public class BruteForceProtector implements Runnable {
return false;
}
- public long getFailures() {
- return failures;
- }
-
- public long getLastFailure() {
- return lastFailure;
- }
-
-
}
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
index b994ef8..b0fd08e 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -27,6 +27,12 @@ public class ClientSessionCode {
private final RealmModel realm;
private final ClientSessionModel clientSession;
+ public enum ActionType {
+ CLIENT,
+ LOGIN,
+ USER
+ }
+
public ClientSessionCode(RealmModel realm, ClientSessionModel clientSession) {
this.realm = realm;
this.clientSession = clientSession;
@@ -128,23 +134,29 @@ public class ClientSessionCode {
return clientSession;
}
- public boolean isValid(String requestedAction) {
+ public boolean isValid(String requestedAction, ActionType actionType) {
if (!isValidAction(requestedAction)) return false;
- return isActionActive(requestedAction);
+ return isActionActive(actionType);
}
- public boolean isActionActive(String requestedAction) {
+ public boolean isActionActive(ActionType actionType) {
int timestamp = clientSession.getTimestamp();
int lifespan;
- if (requestedAction.equals(ClientSessionModel.Action.CODE_TO_TOKEN.name())) {
- lifespan = realm.getAccessCodeLifespan();
-
- } else if (requestedAction.equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
- lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
- } else {
- lifespan = realm.getAccessCodeLifespanUserAction();
+ switch (actionType) {
+ case CLIENT:
+ lifespan = realm.getAccessCodeLifespan();
+ break;
+ case LOGIN:
+ lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction();
+ break;
+ case USER:
+ lifespan = realm.getAccessCodeLifespanUserAction();
+ break;
+ default:
+ throw new IllegalArgumentException();
}
+
return timestamp + lifespan > Time.currentTime();
}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 5f2eeb3..ca1db96 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -110,7 +110,9 @@ public class Messages {
public static final String BEARER_ONLY = "bearerOnlyMessage";
- public static final String DIRECT_GRANTS_ONLY = "directGrantsOnlyMessage";
+ public static final String STANDARD_FLOW_DISABLED = "standardFlowDisabledMessage";
+
+ public static final String IMPLICIT_FLOW_DISABLED = "implicitFlowDisabledMessage";
public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 06a3723..1394832 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -602,12 +602,6 @@ public class AccountService extends AbstractSecuredLocalService {
require(AccountRoles.MANAGE_ACCOUNT);
- String action = formData.getFirst("submitAction");
- if (action != null && action.equals("Cancel")) {
- setReferrerOnPage();
- return account.createResponse(AccountPages.PASSWORD);
- }
-
csrfCheck(formData);
UserModel user = auth.getUser();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
index a7bead8..1ac5dba 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminRoot.java
@@ -9,6 +9,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
@@ -137,11 +138,11 @@ public class AdminRoot {
protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) {
String tokenString = authManager.extractAuthorizationHeaderToken(headers);
if (tokenString == null) throw new UnauthorizedException("Bearer");
- JWSInput input = new JWSInput(tokenString);
AccessToken token;
try {
+ JWSInput input = new JWSInput(tokenString);
token = input.readJsonContent(AccessToken.class);
- } catch (IOException e) {
+ } catch (JWSInputException e) {
throw new UnauthorizedException("Bearer token format error");
}
String realmName = token.getIssuer().substring(token.getIssuer().lastIndexOf('/') + 1);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
index 3cdf988..2a8e924 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AttackDetectionResource.java
@@ -53,7 +53,7 @@ public class AttackDetectionResource {
this.realm = realm;
this.adminEvent = adminEvent.realm(realm);
- auth.init(RealmAuth.Resource.REALM);
+ auth.init(RealmAuth.Resource.USER);
}
/**
@@ -75,7 +75,7 @@ public class AttackDetectionResource {
data.put("lastIPFailure", "n/a");
if (!realm.isBruteForceProtected()) return data;
- UsernameLoginFailureModel model = session.sessions().getUserLoginFailure(realm, username);
+ UsernameLoginFailureModel model = session.sessions().getUserLoginFailure(realm, username.toLowerCase());
if (model == null) return data;
if (protector.isTemporarilyDisabled(session, realm, username)) {
data.put("disabled", true);
@@ -97,7 +97,7 @@ public class AttackDetectionResource {
@DELETE
public void clearBruteForceForUser(@PathParam("username") String username) {
auth.requireManage();
- UsernameLoginFailureModel model = session.sessions().getUserLoginFailure(realm, username);
+ UsernameLoginFailureModel model = session.sessions().getUserLoginFailure(realm, username.toLowerCase());
if (model != null) {
session.sessions().removeUserLoginFailure(realm, username);
adminEvent.operation(OperationType.DELETE).success();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
index 306d350..f53d256 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java
@@ -6,10 +6,12 @@ import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
+import org.keycloak.services.ErrorResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
@@ -86,9 +88,17 @@ public class ProtocolMappersResource {
@Consumes(MediaType.APPLICATION_JSON)
public Response createMapper(ProtocolMapperRepresentation rep) {
auth.requireManage();
- ProtocolMapperModel model = RepresentationToModel.toModel(rep);
- model = client.addProtocolMapper(model);
- adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
+
+ ProtocolMapperModel model = null;
+ try {
+ model = RepresentationToModel.toModel(rep);
+ model = client.addProtocolMapper(model);
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success();
+
+ } catch (ModelDuplicateException e) {
+ return ErrorResponse.exists("Protocol mapper exists with same name");
+ }
+
return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
}
/**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index 901de1f..67bd67e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -144,7 +144,7 @@ public class UsersResource {
}
if (rep.isEnabled() != null && rep.isEnabled()) {
- UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername());
+ UsernameLoginFailureModel failureModel = session.sessions().getUserLoginFailure(realm, rep.getUsername().toLowerCase());
if (failureModel != null) {
failureModel.clearFailures();
}
@@ -828,40 +828,9 @@ public class UsersResource {
@PUT
@Consumes(MediaType.APPLICATION_JSON)
public Response sendVerifyEmail(@PathParam("id") String id, @QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String redirectUri, @QueryParam(OIDCLoginProtocol.CLIENT_ID_PARAM) String clientId) {
- auth.requireManage();
-
- UserModel user = session.users().getUserById(id, realm);
- if (user == null) {
- return ErrorResponse.error("User not found", Response.Status.NOT_FOUND);
- }
-
- if (user.getEmail() == null) {
- return ErrorResponse.error("User email missing", Response.Status.BAD_REQUEST);
- }
-
- ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId);
- ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession);
-
- accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name());
-
- try {
- UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri());
- builder.queryParam("key", accessCode.getCode());
-
- String link = builder.build(realm.getName()).toString();
- long expiration = TimeUnit.SECONDS.toMinutes(realm.getAccessCodeLifespanUserAction());
-
- this.session.getProvider(EmailTemplateProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
-
- //audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getCodeId()).success();
-
- adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
-
- return Response.ok().build();
- } catch (EmailException e) {
- logger.error("Failed to send verification email", e);
- return ErrorResponse.error("Failed to send email", Response.Status.INTERNAL_SERVER_ERROR);
- }
+ List<String> actions = new LinkedList<>();
+ actions.add(UserModel.RequiredAction.VERIFY_EMAIL.name());
+ return executeActionsEmail(id, redirectUri, clientId, actions);
}
private ClientSessionModel createClientSession(UserModel user, String redirectUri, String clientId) {
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 5912536..b31d121 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -446,7 +446,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response cancelled(String code) {
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name())) {
+ if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return redirectToErrorPage(Messages.INVALID_CODE);
}
@@ -456,7 +456,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response error(String code, String message) {
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name())) {
+ if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return redirectToErrorPage(Messages.INVALID_CODE);
}
return browserAuthentication(clientCode.getClientSession(), message);
@@ -522,7 +522,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
private ClientSessionCode parseClientSessionCode(String code) {
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode != null && clientCode.isValid(AUTHENTICATE.name())) {
+ if (clientCode != null && clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
ClientSessionModel clientSession = clientCode.getClientSession();
if (clientSession != null) {
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 125b6ee..c26e60c 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -141,8 +141,10 @@ public class KeycloakApplication extends Application {
if (node == null) {
URL resource = Thread.currentThread().getContextClassLoader().getResource("META-INF/keycloak-server.json");
- log.info("Load config from " + resource);
- node = new ObjectMapper().readTree(resource);
+ if (resource != null) {
+ log.info("Load config from " + resource);
+ node = new ObjectMapper().readTree(resource);
+ }
}
if (node != null) {
@@ -150,7 +152,7 @@ public class KeycloakApplication extends Application {
Config.init(new JsonConfigProvider(node, properties));
return;
} else {
- log.warn("Config 'keycloak-server.json' not found");
+ throw new RuntimeException("Config 'keycloak-server.json' not found");
}
} catch (IOException e) {
throw new RuntimeException("Failed to load config", e);
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 7927991..36e4c6c 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -59,6 +59,8 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
@@ -165,25 +167,25 @@ public class LoginActionsService {
ClientSessionCode clientCode;
Response response;
- boolean verifyCode(String code, String requiredAction) {
+ boolean verifyCode(String code, String requiredAction, ClientSessionCode.ActionType actionType) {
if (!verifyCode(code)) {
return false;
}
- if (!verifyAction(requiredAction)) {
+ if (!verifyAction(requiredAction, actionType)) {
return false;
} else {
return true;
}
}
- public boolean verifyAction(String requiredAction) {
+ public boolean verifyAction(String requiredAction, ClientSessionCode.ActionType actionType) {
if (!clientCode.isValidAction(requiredAction)) {
event.client(clientCode.getClientSession().getClient());
event.error(Errors.INVALID_CODE);
response = ErrorPage.error(session, Messages.INVALID_CODE);
return false;
}
- if (!clientCode.isActionActive(requiredAction)) {
+ if (!clientCode.isActionActive(actionType)) {
event.client(clientCode.getClientSession().getClient());
event.clone().error(Errors.EXPIRED_CODE);
if (clientCode.getClientSession().getAction().equals(ClientSessionModel.Action.AUTHENTICATE.name())) {
@@ -264,7 +266,7 @@ public class LoginActionsService {
@QueryParam("execution") String execution) {
event.event(EventType.LOGIN);
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
}
event.detail(Details.CODE_ID, code);
@@ -315,7 +317,7 @@ public class LoginActionsService {
@QueryParam("execution") String execution) {
event.event(EventType.LOGIN);
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
@@ -374,7 +376,7 @@ public class LoginActionsService {
protected Response resetCredentials(String code, String execution) {
event.event(EventType.RESET_PASSWORD);
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
@@ -438,7 +440,7 @@ public class LoginActionsService {
}
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
}
event.detail(Details.CODE_ID, code);
@@ -468,7 +470,7 @@ public class LoginActionsService {
return ErrorPage.error(session, Messages.REGISTRATION_NOT_ALLOWED);
}
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
}
@@ -496,7 +498,7 @@ public class LoginActionsService {
event.event(EventType.IDENTITY_PROVIDER_FIRST_LOGIN);
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
}
event.detail(Details.CODE_ID, code);
@@ -546,7 +548,7 @@ public class LoginActionsService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processConsent(final MultivaluedMap<String, String> formData) {
- event.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+ event.event(EventType.LOGIN);
if (!checkSsl()) {
@@ -556,43 +558,33 @@ public class LoginActionsService {
String code = formData.getFirst("code");
ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
- if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name())) {
+ if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name(), ClientSessionCode.ActionType.LOGIN)) {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE);
}
ClientSessionModel clientSession = accessCode.getClientSession();
- event.detail(Details.CODE_ID, clientSession.getId());
- String redirect = clientSession.getRedirectUri();
+ initEvent(clientSession);
+
UserSessionModel userSession = clientSession.getUserSession();
UserModel user = userSession.getUser();
ClientModel client = clientSession.getClient();
- event.client(client)
- .user(user)
- .detail(Details.RESPONSE_TYPE, "code")
- .detail(Details.REDIRECT_URI, redirect);
-
- event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
- event.detail(Details.USERNAME, userSession.getLoginUsername());
- if (userSession.isRememberMe()) {
- event.detail(Details.REMEMBER_ME, "true");
- }
-
if (!AuthenticationManager.isSessionValid(realm, userSession)) {
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
- event.session(userSession);
if (formData.containsKey("cancel")) {
LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
protocol.setRealm(realm)
.setHttpHeaders(headers)
- .setUriInfo(uriInfo);
+ .setUriInfo(uriInfo)
+ .setEventBuilder(event);
+ Response response = protocol.sendError(clientSession, Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
- return protocol.sendError(clientSession, Error.CONSENT_DENIED);
+ return response;
}
UserConsentModel grantedConsent = user.getConsentByClient(client.getId());
@@ -613,7 +605,7 @@ public class LoginActionsService {
event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED);
event.success();
- return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection);
+ return authManager.redirectAfterSuccessfulFlow(session, realm, userSession, clientSession, request, uriInfo, clientConnection, event);
}
@Path("email-verification")
@@ -622,7 +614,7 @@ public class LoginActionsService {
event.event(EventType.VERIFY_EMAIL);
if (key != null) {
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -665,7 +657,7 @@ public class LoginActionsService {
return AuthenticationProcessor.createRequiredActionRedirect(realm, clientSession, uriInfo);
} else {
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
@@ -697,7 +689,7 @@ public class LoginActionsService {
event.event(EventType.EXECUTE_ACTIONS);
if (key != null) {
Checks checks = new Checks();
- if (!checks.verifyCode(key, ClientSessionModel.Action.EXECUTE_ACTIONS.name())) {
+ if (!checks.verifyCode(key, ClientSessionModel.Action.EXECUTE_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
ClientSessionModel clientSession = checks.clientCode.getClientSession();
@@ -727,22 +719,27 @@ public class LoginActionsService {
}
private void initEvent(ClientSessionModel clientSession) {
+ UserSessionModel userSession = clientSession.getUserSession();
+
+ String responseType = clientSession.getNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+ if (responseType == null) {
+ responseType = "code";
+ }
+ String respMode = clientSession.getNote(OIDCLoginProtocol.RESPONSE_MODE_PARAM);
+ OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
+
event.event(EventType.LOGIN).client(clientSession.getClient())
- .user(clientSession.getUserSession().getUser())
- .session(clientSession.getUserSession().getId())
+ .user(userSession.getUser())
+ .session(userSession.getId())
.detail(Details.CODE_ID, clientSession.getId())
.detail(Details.REDIRECT_URI, clientSession.getRedirectUri())
.detail(Details.USERNAME, clientSession.getNote(AbstractUsernameFormAuthenticator.ATTEMPTED_USERNAME))
- .detail(Details.RESPONSE_TYPE, "code");
-
- UserSessionModel userSession = clientSession.getUserSession();
-
- if (userSession != null) {
- event.detail(Details.AUTH_METHOD, userSession.getAuthMethod());
- event.detail(Details.USERNAME, userSession.getLoginUsername());
- if (userSession.isRememberMe()) {
- event.detail(Details.REMEMBER_ME, "true");
- }
+ .detail(Details.AUTH_METHOD, userSession.getAuthMethod())
+ .detail(Details.USERNAME, userSession.getLoginUsername())
+ .detail(Details.RESPONSE_TYPE, responseType)
+ .detail(Details.RESPONSE_MODE, responseMode.toString().toLowerCase());
+ if (userSession.isRememberMe()) {
+ event.detail(Details.REMEMBER_ME, "true");
}
}
@@ -767,7 +764,7 @@ public class LoginActionsService {
event.event(EventType.CUSTOM_REQUIRED_ACTION);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
Checks checks = new Checks();
- if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+ if (!checks.verifyCode(code, ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
final ClientSessionCode clientCode = checks.clientCode;
@@ -827,9 +824,14 @@ public class LoginActionsService {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
- .setUriInfo(context.getUriInfo());
- event.detail(Details.CUSTOM_REQUIRED_ACTION, action).error(Errors.REJECTED_BY_USER);
- return protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ .setUriInfo(context.getUriInfo())
+ .setEventBuilder(event);
+
+ event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
+ Response response = protocol.sendError(context.getClientSession(), Error.CONSENT_DENIED);
+ event.error(Errors.REJECTED_BY_USER);
+ return response;
+
}
throw new RuntimeException("Unreachable");
diff --git a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
index 0a15bf1..eeefc81 100644
--- a/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/LocaleHelper.java
@@ -113,15 +113,7 @@ public class LocaleHelper {
RealmModel realm,
String locale) {
boolean secure = realm.getSslRequired().isRequired(session.getContext().getUri().getRequestUri().getHost());
- addCookie(LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, session.getContext().getUri()), null, null, -1, secure, true);
- }
-
- private static void addCookie(String name, String value, String path, String domain, String comment, int maxAge, boolean secure, boolean httpOnly) {
- HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
- StringBuffer cookieBuf = new StringBuffer();
- ServerCookie.appendCookieValue(cookieBuf, 1, name, value, path, domain, comment, maxAge, secure, httpOnly);
- String cookie = cookieBuf.toString();
- response.getOutputHeaders().add(HttpHeaders.SET_COOKIE, cookie);
+ CookieHelper.addCookie(LOCALE_COOKIE, locale, AuthenticationManager.getRealmCookiePath(realm, session.getContext().getUri()), null, null, -1, secure, true);
}
private static Locale findLocale(Set<String> supportedLocales, String... localeStrings) {
diff --git a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
new file mode 100644
index 0000000..b3f77a7
--- /dev/null
+++ b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java
@@ -0,0 +1,41 @@
+package org.keycloak.test;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ResponseTypeTest {
+
+ @Test
+ public void testResponseTypes() {
+ assertFail(null);
+ assertFail("");
+ assertFail("foo");
+ assertSuccess("code");
+ assertSuccess("none");
+ assertFail("id_token");
+ assertFail("token");
+ assertFail("refresh_token");
+ assertSuccess("id_token token");
+ assertSuccess("code token");
+ assertSuccess("code id_token");
+ assertSuccess("code id_token token");
+ assertFail("code none");
+ assertFail("code refresh_token");
+ }
+
+ private void assertSuccess(String responseType) {
+ OIDCResponseType.parse(responseType);
+ }
+
+ private void assertFail(String responseType) {
+ try {
+ OIDCResponseType.parse(responseType);
+ Assert.fail("Not expected to parse '" + responseType + "' with success");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index 5a4b47f..8e9ad53 100755
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -166,7 +166,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
private ClientSessionCode parseClientSessionCode(String code) {
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realm);
- if (clientCode != null && clientCode.isValid(AUTHENTICATE.name())) {
+ if (clientCode != null && clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
ClientSessionModel clientSession = clientCode.getClientSession();
if (clientSession != null) {
testsuite/docker-cluster/pom.xml 4(+2 -2)
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
index f8e5055..00414bf 100755
--- a/testsuite/docker-cluster/pom.xml
+++ b/testsuite/docker-cluster/pom.xml
@@ -21,7 +21,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
<dependency>
@@ -69,7 +69,7 @@
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
<version>${project.version}</version>
<outputDirectory>${project.build.directory}/wildfly-adapter</outputDirectory>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 9458998..d02c255 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -28,6 +28,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.migration.MigrationModel;
@@ -468,6 +469,104 @@ public class AccountTest {
}
}
+ @Test
+ public void changeUsernameLoginWithOldUsername() {
+ KeycloakSession session = keycloakRule.startSession();
+ UserModel user = session.users().addUser(session.realms().getRealm("test"), "change-username");
+ user.setEnabled(true);
+ user.setEmail("change-username@localhost");
+ user.setFirstName("first");
+ user.setLastName("last");
+ user.updateCredential(UserCredentialModel.password("password"));
+ keycloakRule.stopSession(session, true);
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(true);
+ }
+ });
+
+ try {
+ profilePage.open();
+ loginPage.login("change-username", "password");
+
+ profilePage.updateUsername("change-username-updated");
+
+ Assert.assertEquals("Your account has been updated.", profilePage.getSuccess());
+
+ profilePage.logout();
+
+ profilePage.open();
+
+ Assert.assertTrue(loginPage.isCurrent());
+
+ loginPage.login("change-username", "password");
+
+ Assert.assertTrue(loginPage.isCurrent());
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+ loginPage.login("change-username-updated", "password");
+ } finally {
+ events.clear();
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(false);
+ }
+ });
+ }
+ }
+
+ @Test
+ public void changeEmailLoginWithOldEmail() {
+ KeycloakSession session = keycloakRule.startSession();
+ UserModel user = session.users().addUser(session.realms().getRealm("test"), "change-email");
+ user.setEnabled(true);
+ user.setEmail("change-username@localhost");
+ user.setFirstName("first");
+ user.setLastName("last");
+ user.updateCredential(UserCredentialModel.password("password"));
+ keycloakRule.stopSession(session, true);
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(true);
+ }
+ });
+
+ try {
+ profilePage.open();
+ loginPage.login("change-username@localhost", "password");
+
+ profilePage.updateEmail("change-username-updated@localhost");
+
+ Assert.assertEquals("Your account has been updated.", profilePage.getSuccess());
+
+ profilePage.logout();
+
+ profilePage.open();
+
+ Assert.assertTrue(loginPage.isCurrent());
+
+ loginPage.login("change-username@localhost", "password");
+
+ Assert.assertTrue(loginPage.isCurrent());
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+ loginPage.login("change-username-updated@localhost", "password");
+ } finally {
+ events.clear();
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setEditUsernameAllowed(false);
+ }
+ });
+ }
+ }
+
// KEYCLOAK-1534
@Test
public void changeEmailToExisting() {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index c7f075f..be609b8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -277,7 +277,7 @@ public class RequiredActionEmailVerificationTest {
.assertEvent();
}
- private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
+ public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
Multipart multipart = (Multipart) message.getContent();
final String textContentType = multipart.getBodyPart(0).getContentType();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index 9e51065..8147cb3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -255,6 +255,7 @@ public class AdminAPITest {
Assert.assertEquals(rep.getAccessCodeLifespanUserAction(), storedRealm.getAccessCodeLifespanUserAction());
if (rep.getNotBefore() != null) Assert.assertEquals(rep.getNotBefore(), storedRealm.getNotBefore());
if (rep.getAccessTokenLifespan() != null) Assert.assertEquals(rep.getAccessTokenLifespan(), storedRealm.getAccessTokenLifespan());
+ if (rep.getAccessTokenLifespanForImplicitFlow() != null) Assert.assertEquals(rep.getAccessTokenLifespanForImplicitFlow(), storedRealm.getAccessTokenLifespanForImplicitFlow());
if (rep.getSsoSessionIdleTimeout() != null) Assert.assertEquals(rep.getSsoSessionIdleTimeout(), storedRealm.getSsoSessionIdleTimeout());
if (rep.getSsoSessionMaxLifespan() != null) Assert.assertEquals(rep.getSsoSessionMaxLifespan(), storedRealm.getSsoSessionMaxLifespan());
if (rep.getRequiredCredentials() != null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index f262f34..0acd7a0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -6,6 +6,8 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.UserResource;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
@@ -15,9 +17,9 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.actions.RequiredActionEmailVerificationTest;
import org.keycloak.testsuite.forms.ResetPasswordTest;
-import org.keycloak.testsuite.pages.LoginPasswordResetPage;
-import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
+import org.keycloak.testsuite.pages.*;
import org.keycloak.testsuite.rule.GreenMailRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
@@ -58,6 +60,9 @@ public class UserTest extends AbstractClientTest {
@WebResource
protected WebDriver driver;
+ @WebResource
+ protected InfoPage infoPage;
+
@Before
public void before() {
super.before();
@@ -475,7 +480,7 @@ public class UserTest extends AbstractClientTest {
@Test
- public void sendVerifyEmail() {
+ public void sendVerifyEmail() throws IOException, MessagingException {
UserRepresentation userRep = new UserRepresentation();
userRep.setUsername("user1");
Response response = realm.users().create(userRep);
@@ -517,6 +522,15 @@ public class UserTest extends AbstractClientTest {
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
Assert.assertEquals("invalidClientId not enabled", error.getErrorMessage());
}
+
+ user.sendVerifyEmail();
+ assertEquals(1, greenMail.getReceivedMessages().length);
+
+ String link = RequiredActionEmailVerificationTest.getPasswordResetEmailLink(greenMail.getReceivedMessages()[0]);
+
+ driver.navigate().to(link);
+
+ Assert.assertEquals("Your account has been updated.", infoPage.getInfo());
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 74c70ea..84e724b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -8,6 +8,7 @@ import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.Config;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.common.constants.ServiceAccountConstants;
import org.keycloak.events.admin.AdminEvent;
@@ -134,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
return expect(EventType.CLIENT_LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
- .detail(Details.RESPONSE_TYPE, ServiceAccountConstants.CLIENT_AUTH)
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
.removeDetail(Details.CODE_ID)
.session(isUUID());
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
index 8f0778c..4e654aa 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
@@ -303,6 +303,15 @@ public class BruteForceTest {
}
@Test
+ public void testBrowserInvalidPasswordDifferentCase() throws Exception {
+ loginSuccess("test-user@localhost");
+ loginInvalidPassword("test-User@localhost");
+ loginInvalidPassword("Test-user@localhost");
+ expectTemporarilyDisabled();
+ clearAllUserFailures();
+ }
+
+ @Test
public void testBrowserMissingPassword() throws Exception {
loginSuccess();
loginMissingPassword();
@@ -333,8 +342,12 @@ public class BruteForceTest {
}
public void expectTemporarilyDisabled() throws Exception {
+ expectTemporarilyDisabled("test-user@localhost");
+ }
+
+ public void expectTemporarilyDisabled(String username) throws Exception {
loginPage.open();
- loginPage.login("test-user@localhost", "password");
+ loginPage.login(username, "password");
loginPage.assertCurrent();
String src = driver.getPageSource();
@@ -345,9 +358,11 @@ public class BruteForceTest {
.assertEvent();
}
-
-
public void loginSuccess() throws Exception {
+ loginSuccess("test-user@localhost");
+ }
+
+ public void loginSuccess(String username) throws Exception {
loginPage.open();
loginPage.login("test-user@localhost", "password");
@@ -391,10 +406,13 @@ public class BruteForceTest {
events.clear();
}
-
public void loginInvalidPassword() throws Exception {
+ loginInvalidPassword("test-user@localhost");
+ }
+
+ public void loginInvalidPassword(String username) throws Exception {
loginPage.open();
- loginPage.login("test-user@localhost", "invalid");
+ loginPage.login(username, "invalid");
loginPage.assertCurrent();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
index f704553..4690308 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java
@@ -218,7 +218,7 @@ public class CustomFlowTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 1acd654..0c653e0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -21,10 +21,7 @@
*/
package org.keycloak.testsuite.forms;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.*;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.Event;
@@ -39,13 +36,8 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.MailUtil;
import org.keycloak.testsuite.OAuthClient;
-import org.keycloak.testsuite.pages.AppPage;
+import org.keycloak.testsuite.pages.*;
import org.keycloak.testsuite.pages.AppPage.RequestType;
-import org.keycloak.testsuite.pages.ErrorPage;
-import org.keycloak.testsuite.pages.InfoPage;
-import org.keycloak.testsuite.pages.LoginPage;
-import org.keycloak.testsuite.pages.LoginPasswordResetPage;
-import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.rule.GreenMailRule;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
@@ -59,6 +51,7 @@ import javax.mail.internet.MimeMessage;
import java.io.IOException;
import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
@@ -115,6 +108,9 @@ public class ResetPasswordTest {
protected InfoPage infoPage;
@WebResource
+ protected VerifyEmailPage verifyEmailPage;
+
+ @WebResource
protected LoginPasswordResetPage resetPasswordPage;
@WebResource
@@ -423,6 +419,59 @@ public class ResetPasswordTest {
}
@Test
+ public void resetPasswordExpiredCodeShort() throws IOException, MessagingException, InterruptedException {
+ final AtomicInteger originalValue = new AtomicInteger();
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ originalValue.set(appRealm.getAccessCodeLifespan());
+ appRealm.setAccessCodeLifespanUserAction(60);
+ }
+ });
+
+ try {
+ loginPage.open();
+ loginPage.resetPassword();
+
+ resetPasswordPage.assertCurrent();
+
+ resetPasswordPage.changePassword("login-test");
+
+ loginPage.assertCurrent();
+ assertEquals("You should receive an email shortly with further instructions.", loginPage.getSuccessMessage());
+
+ events.expectRequiredAction(EventType.SEND_RESET_PASSWORD)
+ .session((String)null)
+ .user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
+
+ assertEquals(1, greenMail.getReceivedMessages().length);
+
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+
+ String changePasswordUrl = getPasswordResetEmailLink(message);
+
+ Time.setOffset(70);
+
+ driver.navigate().to(changePasswordUrl.trim());
+
+ loginPage.assertCurrent();
+
+ assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
+
+ events.expectRequiredAction(EventType.RESET_PASSWORD).error("expired_code").client("test-app").user((String) null).session((String) null).clearDetails().assertEvent();
+ } finally {
+ Time.setOffset(0);
+
+ keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.setAccessCodeLifespanUserAction(originalValue.get());
+ }
+ });
+ }
+ }
+
+ @Test
public void resetPasswordDisabledUser() throws IOException, MessagingException, InterruptedException {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
index 97250c4..a5f2388 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java
@@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.admin.client.resource.GroupResource;
import org.keycloak.admin.client.resource.RealmResource;
@@ -256,7 +257,7 @@ public class GroupTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index b2d14b9..99e5b95 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -74,6 +74,7 @@ public class ImportTest extends AbstractModelTest {
public static void assertDataImportedInRealm(KeycloakSession session, RealmModel realm) {
Assert.assertTrue(realm.isVerifyEmail());
Assert.assertEquals(3600000, realm.getOfflineSessionIdleTimeout());
+ Assert.assertEquals(1500, realm.getAccessTokenLifespanForImplicitFlow());
List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
Assert.assertEquals(1, creds.size());
@@ -327,6 +328,13 @@ public class ImportTest extends AbstractModelTest {
Assert.assertFalse(otherAppAdminConsent.isRoleGranted(application.getRole("app-admin")));
Assert.assertTrue(otherAppAdminConsent.isProtocolMapperGranted(gssCredentialMapper));
+ Assert.assertTrue(application.isStandardFlowEnabled());
+ Assert.assertTrue(application.isImplicitFlowEnabled());
+ Assert.assertTrue(application.isDirectAccessGrantsEnabled());
+ Assert.assertFalse(otherApp.isStandardFlowEnabled());
+ Assert.assertFalse(otherApp.isImplicitFlowEnabled());
+ Assert.assertFalse(otherApp.isDirectAccessGrantsEnabled());
+
// Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
@@ -344,6 +352,7 @@ public class ImportTest extends AbstractModelTest {
RealmModel realm =manager.importRealm(rep);
Assert.assertEquals(600, realm.getAccessCodeLifespanUserAction());
+ Assert.assertEquals(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT, realm.getAccessTokenLifespanForImplicitFlow());
Assert.assertEquals(Constants.DEFAULT_OFFLINE_SESSION_IDLE_TIMEOUT, realm.getOfflineSessionIdleTimeout());
verifyRequiredCredentials(realm.getRequiredCredentials(), "password");
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index e191871..1b6cbd6 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -38,6 +38,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.Event;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
@@ -806,26 +807,15 @@ public class AccessTokenTest {
}
}
- private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
+ private IDToken getIdToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws JWSInputException {
JWSInput input = new JWSInput(tokenResponse.getIdToken());
- IDToken idToken = null;
- try {
- idToken = input.readJsonContent(IDToken.class);
- } catch (IOException e) {
- throw new VerificationException();
- }
- return idToken;
+ return input.readJsonContent(IDToken.class);
}
- private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws VerificationException {
+ private AccessToken getAccessToken(org.keycloak.representations.AccessTokenResponse tokenResponse) throws JWSInputException {
JWSInput input = new JWSInput(tokenResponse.getToken());
- AccessToken idToken = null;
- try {
- idToken = input.readJsonContent(AccessToken.class);
- } catch (IOException e) {
- throw new VerificationException();
- }
- return idToken;
+ return input.readJsonContent(AccessToken.class);
+
}
protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index d3a47e0..5d5e0ed 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -30,6 +30,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.utils.OIDCResponseType;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents;
@@ -160,12 +161,21 @@ public class AuthorizationCodeTest {
}
@Test
+ public void authorizationRequestImplicitFlowDisabled() throws IOException {
+ UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
+ b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token id_token");
+ driver.navigate().to(b.build().toURL());
+ assertEquals("Client is not allowed to initiate browser login with given response_type. Implicit flow is disabled for the client.", errorPage.getError());
+ events.expectLogin().error(Errors.NOT_ALLOWED).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token id_token").assertEvent();
+ }
+
+ @Test
public void authorizationRequestInvalidResponseType() throws IOException {
UriBuilder b = UriBuilder.fromUri(oauth.getLoginFormUrl());
b.replaceQueryParam(OAuth2Constants.RESPONSE_TYPE, "token");
driver.navigate().to(b.build().toURL());
assertEquals("Invalid parameter: response_type", errorPage.getError());
- events.expectLogin().error(Errors.INVALID_REQUEST).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
+ events.expectLogin().error(Errors.INVALID_REQUEST).client((String) null).user((String) null).session((String) null).clearDetails().detail(Details.RESPONSE_TYPE, "token").assertEvent();
}
private void assertCode(String expectedCodeId, String actualCode) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
index 537a297..898066a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java
@@ -189,7 +189,7 @@ public class ClientAuthSignedJWTTest {
events.expectLogin()
.client("client2")
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, "test-user@localhost")
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index 6c205ad..d333f86 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -40,6 +40,7 @@ import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.io.IOException;
+import java.net.URL;
/**
* @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
@@ -174,7 +175,10 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
- Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?code="));
+ URL url = new URL(driver.getCurrentUrl());
+ Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+ Assert.assertTrue(url.getQuery().contains("code="));
+ Assert.assertTrue(url.getQuery().contains("state="));
}
@Test
@@ -192,7 +196,11 @@ public class OAuthRedirectUriTest {
OAuthClient.AuthorizationCodeResponse response = oauth.doLogin("test-user@localhost", "password");
Assert.assertNotNull(response.getCode());
- Assert.assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/app?key=value&code="));
+ URL url = new URL(driver.getCurrentUrl());
+ Assert.assertTrue(url.toString().startsWith("http://localhost:8081/app"));
+ Assert.assertTrue(url.getQuery().contains("key=value"));
+ Assert.assertTrue(url.getQuery().contains("state="));
+ Assert.assertTrue(url.getQuery().contains("code="));
}
@Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 219fa4f..a4b2855 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -25,6 +25,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
@@ -318,7 +319,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
@@ -360,7 +361,7 @@ public class OfflineTokenTest {
.client("offline-client")
.user(userId)
.session(token.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, token.getId())
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
@@ -645,7 +646,12 @@ public class OfflineTokenTest {
StringBuilder response = new StringBuilder("<html><head><title>Offline token servlet</title></head><body><pre>");
RefreshableKeycloakSecurityContext ctx = (RefreshableKeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(ctx.getToken());
- RefreshToken refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
+ RefreshToken refreshToken = null;
+ try {
+ refreshToken = new JWSInput(ctx.getRefreshToken()).readJsonContent(RefreshToken.class);
+ } catch (JWSInputException e) {
+ throw new IOException(e);
+ }
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
response = response.append(accessTokenPretty)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 15f7f93..2373489 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -116,6 +116,14 @@ public class RefreshTokenTest {
}
@Test
+ public void invalidRefreshToken() throws Exception {
+ AccessTokenResponse response = oauth.doRefreshTokenRequest("invalid", "password");
+ Assert.assertEquals(400, response.getStatusCode());
+ Assert.assertEquals("invalid_grant", response.getError());
+ events.clear();
+ }
+
+ @Test
public void refreshTokenRequest() throws Exception {
oauth.doLogin("test-user@localhost", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
index 74e3d38..b64a683 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java
@@ -6,6 +6,7 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -93,7 +94,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
@@ -129,7 +130,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session(accessToken.getSessionState())
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.removeDetail(Details.CODE_ID)
@@ -285,7 +286,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
events.expectLogin()
.client("resource-owner")
.session((String) null)
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
@@ -307,7 +308,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.client("resource-owner")
.user((String) null)
.session((String) null)
- .detail(Details.RESPONSE_TYPE, "token")
+ .detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.USERNAME, "invalid")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
index c013345..1fe6203 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java
@@ -87,6 +87,18 @@ public class AccountUpdateProfilePage extends AbstractAccountPage {
submitButton.click();
}
+ public void updateUsername(String username) {
+ usernameInput.clear();
+ usernameInput.sendKeys(username);
+ submitButton.click();
+ }
+
+ public void updateEmail(String email) {
+ emailInput.clear();
+ emailInput.sendKeys(email);
+ submitButton.click();
+ }
+
public void clickCancel() {
cancelButton.click();
}
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 68ec4d3..af86592 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -2,6 +2,7 @@
"realm": "test-realm",
"enabled": true,
"accessTokenLifespan": 6000,
+ "accessTokenLifespanForImplicitFlow": 1500,
"accessCodeLifespan": 30,
"accessCodeLifespanUserAction": 600,
"offlineSessionIdleTimeout": 3600000,
@@ -154,6 +155,7 @@
"clientId": "Application",
"name": "Applicationn",
"enabled": true,
+ "implicitFlowEnabled": true,
"nodeReRegistrationTimeout": 50,
"registeredNodes": {
"node1": 10,
@@ -164,6 +166,8 @@
"clientId": "OtherApp",
"name": "Other Application",
"enabled": true,
+ "standardFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
"serviceAccountsEnabled": true,
"clientAuthenticatorType": "client-jwt",
"protocolMappers" : [
diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml
index 0eb108c..fb9b4d5 100644
--- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml
+++ b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml
@@ -1,6 +1,6 @@
<assembly>
- <id>auth-server-wildfly-kc14</id>
+ <id>auth-server-wildfly-kc15</id>
<formats>
<format>zip</format>
diff --git a/testsuite/integration-arquillian/servers/wildfly/pom.xml b/testsuite/integration-arquillian/servers/wildfly/pom.xml
index 9e0ecdb..d7c6a8c 100644
--- a/testsuite/integration-arquillian/servers/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/servers/wildfly/pom.xml
@@ -167,7 +167,7 @@
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
</dependencies>
@@ -187,7 +187,7 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${keycloak.server.home}</outputDirectory>
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly/pom.xml b/testsuite/integration-arquillian/tests/adapters/wildfly/pom.xml
index 428bdf1..81d9804 100644
--- a/testsuite/integration-arquillian/tests/adapters/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly/pom.xml
@@ -13,7 +13,7 @@
<properties>
<app.server.wildfly.home>${containers.home}/wildfly-${wildfly.version}</app.server.wildfly.home>
- <adapter.libs.wildfly>${containers.home}/keycloak-wf9-adapter-dist</adapter.libs.wildfly>
+ <adapter.libs.wildfly>${containers.home}/keycloak-wildfly-adapter-dist</adapter.libs.wildfly>
</properties>
<dependencies>
@@ -28,7 +28,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
</dependencies>
@@ -63,7 +63,7 @@
</artifactItem>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${adapter.libs.wildfly}</outputDirectory>
diff --git a/testsuite/integration-arquillian/tests/adapters/wildfly-relative/pom.xml b/testsuite/integration-arquillian/tests/adapters/wildfly-relative/pom.xml
index 92d757a..6c78ff2 100644
--- a/testsuite/integration-arquillian/tests/adapters/wildfly-relative/pom.xml
+++ b/testsuite/integration-arquillian/tests/adapters/wildfly-relative/pom.xml
@@ -13,7 +13,7 @@
<properties>
- <adapter.libs.wildfly>${containers.home}/keycloak-wf9-adapter-dist</adapter.libs.wildfly>
+ <adapter.libs.wildfly>${containers.home}/keycloak-wildfly-adapter-dist</adapter.libs.wildfly>
<!--this is needed for adapter tests that load system properties in adapter config-->
<app.server.http.port>${auth.server.http.port}</app.server.http.port>
@@ -27,7 +27,7 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<type>zip</type>
</dependency>
</dependencies>
@@ -56,7 +56,7 @@
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-wf9-adapter-dist</artifactId>
+ <artifactId>keycloak-wildfly-adapter-dist</artifactId>
<version>${project.version}</version>
<type>zip</type>
<outputDirectory>${adapter.libs.wildfly}</outputDirectory>
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecution.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecution.java
new file mode 100644
index 0000000..f572cdf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecution.java
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class CreateExecution extends Authentication {
+
+ @Page
+ private CreateExecutionForm form;
+
+ public CreateExecutionForm form() {
+ return form;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecutionForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecutionForm.java
new file mode 100644
index 0000000..eef0389
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateExecutionForm.java
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class CreateExecutionForm extends Form {
+ public enum ProviderOption {
+ DIRECT_GRANT_VALIDATE_USERNAME("direct-grant-validate-username"),
+ RESET_OTP("reset-otp"),
+ AUTH_COOKIE("auth-cookie"),
+ RESET_CREDENTIALS_CHOOSE_USER("reset-credentials-choose-user"),
+ DIRECT_GRANT_VALIDATE_PASSWORD("direct-grant-validate-password"),
+ AUTH_USERNAME_PASSWORD_FORM("auth-username-password-form"),
+ AUTH_OTP_FORM("auth-otp-form"),
+ AUTH_SPNEGO("auth-spnego"),
+ DIRECT_GRANT_VALIDATE_OPT("direct-grant-validate-otp"),
+ RESET_CREDENTIALS_EMAIL("reset-credential-email"),
+ RESET_PASSWORD("reset-password");
+
+ private final String name;
+
+ private ProviderOption(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @FindBy(id = "provider")
+ private Select providerSelect;
+
+ public void selectProviderOption(ProviderOption value) {
+ providerSelect.selectByVisibleText(value.getName());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlow.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlow.java
new file mode 100644
index 0000000..18c29f2
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlow.java
@@ -0,0 +1,44 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class CreateFlow extends Authentication {
+
+ @Page
+ private CreateFlowForm form;
+
+ @Override
+ public String getUriFragment() {
+ return super.getUriFragment() + "/create/flow";
+ }
+
+ public CreateFlowForm form() {
+ return form;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlowForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlowForm.java
new file mode 100644
index 0000000..8eb1483
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/CreateFlowForm.java
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import org.keycloak.testsuite.page.Form;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class CreateFlowForm extends Form {
+
+ @FindBy(id = "alias")
+ private WebElement aliasInput;
+
+ @FindBy(id = "description")
+ private WebElement descriptionTextarea;
+
+ @FindBy(id = "flowType")
+ private Select flowTypeSelect;
+
+ public enum FlowType {
+
+ GENERIC("generic"),
+ FORM("form"),
+ CLIENT("client");
+
+ private final String name;
+
+ private FlowType(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public void setValues(String alias, String description, FlowType flowType) {
+ setInputValue(aliasInput, alias);
+ setInputValue(descriptionTextarea, description);
+ flowTypeSelect.selectByVisibleText(flowType.getName());
+ save();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/Flows.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/Flows.java
new file mode 100644
index 0000000..63694cc
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/Flows.java
@@ -0,0 +1,90 @@
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import org.keycloak.testsuite.console.page.authentication.Authentication;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ * @author tkyjovsk
+ * @author mhajas
+ */
+public class Flows extends Authentication {
+
+ @Override
+ public String getUriFragment() {
+ return super.getUriFragment() + "/flows";
+ }
+
+ @FindBy(tagName = "select")
+ private Select flowSelect;
+
+ @FindBy(xpath = "//button[text() = 'New']")
+ private WebElement newButton;
+
+ @FindBy(xpath = "//button[text() = 'Copy']")
+ private WebElement copyButton;
+
+ @FindBy(xpath = "//button[text() = 'Delete']")
+ private WebElement deleteButton;
+
+ @FindBy(xpath = "//button[text() = 'Add Execution']")
+ private WebElement addExecutionButton;
+
+ @FindBy(xpath = "//button[text() = 'Add Flow']")
+ private WebElement addFlowButton;
+
+ @FindBy(tagName = "table")
+ private FlowsTable flowsTable;
+
+ public enum FlowOption {
+
+ DIRECT_GRANT("Direct grant"),
+ REGISTRATION("Registration"),
+ BROWSER("Browser"),
+ RESET_CREDENTIALS("Reset credentials"),
+ CLIENTS("Clients");
+
+ private final String name;
+
+ private FlowOption(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public void selectFlowOption(FlowOption option) {
+ flowSelect.selectByVisibleText(option.getName());
+ }
+
+ public String getFlowSelectValue() {
+ return flowSelect.getFirstSelectedOption().getText();
+ }
+
+ public FlowsTable table() {
+ return flowsTable;
+ }
+
+ public void clickNew() {
+ newButton.click();
+ }
+
+ public void clickCopy() {
+ copyButton.click();
+ }
+
+ public void clickDelete() {
+ deleteButton.click();
+ }
+
+ public void clickAddExecution() {
+ addExecutionButton.click();
+ }
+
+ public void clickAddFlow() {
+ addFlowButton.click();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/FlowsTable.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/FlowsTable.java
new file mode 100644
index 0000000..ad64d37
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/flows/FlowsTable.java
@@ -0,0 +1,93 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.page.authentication.flows;
+
+import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+public class FlowsTable {
+ public enum RequirementOption {
+
+ ALTERNATIVE("ALTERNATIVE"),
+ DISABLED("DISABLED"),
+ OPTIONAL("OPTIONAL"),
+ REQUIRED("REQUIRED");
+
+ private final String name;
+
+ private RequirementOption(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ public enum Action {
+
+ DELETE("Delete"),
+ ADD_EXECUTION("Add Execution"),
+ ADD_FLOW("Add Flow");
+
+ private final String name;
+
+ private Action(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ @FindBy(tagName = "tbody")
+ private WebElement tbody;
+
+ private WebElement getRowByLabelText(String text) {
+ WebElement row = tbody.findElement(By.xpath("//span[text() = '" + text + "']/../.."));
+ waitAjaxForElement(row);
+ return row;
+ }
+
+ public void clickLevelUpButton(String rowLabel) {
+ getRowByLabelText(rowLabel).findElement(By.xpath("//i[contains(@class, 'up')]/..")).click();
+ }
+
+ public void clickLevelDownButton(String rowLabel) {
+ getRowByLabelText(rowLabel).findElement(By.xpath("//i[contains(@class, 'down')]/..")).click();
+ }
+
+ public void changeRequirement(String rowLabel, RequirementOption option) {
+ getRowByLabelText(rowLabel).findElement(By.xpath("//input[@value = '" + option + "']")).click();
+ }
+
+ public void performAction(String rowLabel, Action action) {
+
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
index 87eadb5..2be0435 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientForm.java
@@ -32,8 +32,14 @@ public class CreateClientForm extends Form {
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
private OnOffSwitch consentRequiredSwitch;
- @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directGrantsOnly']]")
- private OnOffSwitch directGrantsOnlySwitch;
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='standardFlowEnabled']]")
+ private OnOffSwitch standardFlowEnabledSwitch;
+
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='implicitFlowEnabled']]")
+ private OnOffSwitch implicitFlowEnabledSwitch;
+
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='directAccessGrantsEnabled']]")
+ private OnOffSwitch directAccessGrantsEnabledSwitch;
@FindBy(id = "protocol")
private Select protocolSelect;
@@ -69,7 +75,9 @@ public class CreateClientForm extends Form {
setName(client.getName());
setEnabled(client.isEnabled());
setConsentRequired(client.isConsentRequired());
- setDirectGrantsOnly(client.isDirectGrantsOnly());
+ setStandardFlowEnabled(client.isStandardFlowEnabled());
+ setImplicitFlowEnabled(client.isImplicitFlowEnabled());
+ setDirectAccessGrantsEnabled(client.isDirectAccessGrantsEnabled());
setProtocol(client.getProtocol());
if (OIDC.equals(client.getProtocol())) {
setAccessType(client);
@@ -88,7 +96,9 @@ public class CreateClientForm extends Form {
values.setName(getName());
values.setEnabled(isEnabled());
values.setConsentRequired(isConsentRequired());
- values.setDirectGrantsOnly(isDirectGrantsOnly());
+ values.setStandardFlowEnabled(isStandardFlowEnabled());
+ values.setImplicitFlowEnabled(isImplicitFlowEnabled());
+ values.setDirectAccessGrantsEnabled(isDirectAccessGrantsEnabled());
values.setProtocol(getProtocol());
if (OIDC.equals(values.getProtocol())) {
values.setBearerOnly(isBearerOnly());
@@ -195,12 +205,28 @@ public class CreateClientForm extends Form {
consentRequiredSwitch.setOn(consentRequired);
}
- public boolean isDirectGrantsOnly() {
- return directGrantsOnlySwitch.isOn();
+ public boolean isStandardFlowEnabled() {
+ return standardFlowEnabledSwitch.isOn();
+ }
+
+ public void setStandardFlowEnabled(boolean standardFlowEnabled) {
+ standardFlowEnabledSwitch.setOn(standardFlowEnabled);
+ }
+
+ public boolean isImplicitFlowEnabled() {
+ return implicitFlowEnabledSwitch.isOn();
+ }
+
+ public void setImplicitFlowEnabled(boolean implicitFlowEnabled) {
+ implicitFlowEnabledSwitch.setOn(implicitFlowEnabled);
+ }
+
+ public boolean isDirectAccessGrantsEnabled() {
+ return directAccessGrantsEnabledSwitch.isOn();
}
- public void setDirectGrantsOnly(boolean directGrantsOnly) {
- directGrantsOnlySwitch.setOn(directGrantsOnly);
+ public void setDirectAccessGrantsEnabled(boolean directAccessGrantsEnabled) {
+ directAccessGrantsEnabledSwitch.setOn(directAccessGrantsEnabled);
}
public String getProtocol() {
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/ModalDialog.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/ModalDialog.java
index 9bb95dd..36677b4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/ModalDialog.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/ModalDialog.java
@@ -17,6 +17,8 @@ public class ModalDialog {
@FindBy(xpath = ".//button[@ng-click='ok()']")
private WebElement okButton;
+ @FindBy(id = "name")
+ private WebElement nameInput;
public void ok() {
waitAjaxForElement(okButton);
@@ -32,5 +34,11 @@ public class ModalDialog {
waitAjaxForElement(cancelButton);
cancelButton.click();
}
+
+ public void setName(String name) {
+ waitAjaxForElement(nameInput);
+ nameInput.clear();
+ nameInput.sendKeys(name);
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/FlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/FlowsTest.java
new file mode 100644
index 0000000..7b2a874
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/authentication/FlowsTest.java
@@ -0,0 +1,196 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.console.authentication;
+
+import org.jboss.arquillian.graphene.page.Page;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.testsuite.console.AbstractConsoleTest;
+import org.keycloak.testsuite.console.page.authentication.flows.CreateExecution;
+import org.keycloak.testsuite.console.page.authentication.flows.CreateExecutionForm;
+import org.keycloak.testsuite.console.page.authentication.flows.CreateFlow;
+import org.keycloak.testsuite.console.page.authentication.flows.CreateFlowForm;
+import org.keycloak.testsuite.console.page.authentication.flows.Flows;
+import org.keycloak.testsuite.console.page.authentication.flows.FlowsTable;
+
+/**
+ *
+ * @author <a href="mailto:vramik@redhat.com">Vlastislav Ramik</a>
+ */
+@Ignore //waiting for KEYCLOAK-1967(KEYCLOAK-1966)
+public class FlowsTest extends AbstractConsoleTest {
+
+ @Page
+ private Flows flowsPage;
+
+ @Page
+ private CreateFlow createFlowPage;
+
+ @Page
+ private CreateExecution createExecutionPage;
+
+ @Before
+ public void beforeFlowsTest() {
+ flowsPage.navigateTo();
+ }
+
+ @Test
+ public void createDeleteFlowTest() {
+ log.info("add new flow");
+ flowsPage.clickNew();
+ createFlowPage.form().setValues("testFlow", "testDesc", CreateFlowForm.FlowType.GENERIC);
+ assertEquals("Success! Flow Created.", createFlowPage.getSuccessMessage());
+ log.debug("new flow created via UI");
+
+ log.info("check if test flow is created via rest");
+ //rest: flow is present
+ log.debug("checked");
+
+ log.debug("check if testFlow is selected in UI");
+ assertEquals("TestFlow", flowsPage.getFlowSelectValue());
+
+ log.info("add new execution flow within testFlow");
+ flowsPage.clickAddFlow();
+ createFlowPage.form().setValues("testExecutionFlow", "executionDesc", CreateFlowForm.FlowType.GENERIC);
+ assertEquals("Success! Flow Created.", createFlowPage.getSuccessMessage());
+ log.debug("new execution flow created via UI");
+
+ log.info("check if execution flow is created via rest");
+ //rest: flow within nested flow is present
+ log.debug("checked");
+
+ log.debug("check if testFlow is selected in UI");
+ assertEquals("TestFlow", flowsPage.getFlowSelectValue());
+
+ log.info("delete test flow");
+ flowsPage.clickDelete();
+ assertEquals("Success! Flow removed", createFlowPage.getSuccessMessage());
+ log.debug("test flow removed via UI");
+
+ log.info("check if both test flow and execution flow is removed via rest");
+ //rest
+ log.debug("checked");
+ }
+
+ @Test
+ public void createFlowWithEmptyAliasTest() {
+ flowsPage.clickNew();
+ createFlowPage.form().setValues("", "testDesc", CreateFlowForm.FlowType.GENERIC);
+ assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", createFlowPage.getErrorMessage());
+
+ //rest:flow isn't present
+
+ //best-efford: check empty alias in nested flow
+ }
+
+ @Test
+ public void copyFlowTest() {
+ flowsPage.selectFlowOption(Flows.FlowOption.BROWSER);
+ flowsPage.clickCopy();
+
+ modalDialog.setName("test copy of browser");
+ modalDialog.ok();
+ assertEquals("Success! Flow copied.", createFlowPage.getSuccessMessage());
+
+ //rest: copied flow present
+ }
+
+ @Test
+ public void createDeleteExecutionTest() {
+ //rest: add new flow
+
+ log.info("add new execution within testFlow");
+ flowsPage.clickAddExecution();
+ createExecutionPage.form().selectProviderOption(CreateExecutionForm.ProviderOption.RESET_PASSWORD);
+ createExecutionPage.form().save();
+
+ assertEquals("Success! Execution Created.", createExecutionPage.getSuccessMessage());
+ log.debug("new execution flow created via UI");
+
+ //rest:check new execution
+
+ log.debug("check if testFlow is selected in UI");
+ assertEquals("TestFlow", flowsPage.getFlowSelectValue());
+
+ log.info("delete test flow");
+ flowsPage.clickDelete();
+ assertEquals("Success! Flow removed", createFlowPage.getSuccessMessage());
+ log.debug("test flow removed via UI");
+
+ log.info("check if both test flow and execution flow is removed via rest");
+ //rest
+ log.debug("checked");
+ }
+
+ @Test
+ public void navigationTest() {
+ //rest: add or copy flow to test navigation (browser)
+
+ //rest:
+ log.debug("check if there is expected structure of the flow");
+ //first should be Cookie
+ //second Kerberos
+ //third Test Copy Of Browser Forms
+ //a) Username Password Form
+ //b) OTP Form
+
+
+ flowsPage.table().clickLevelDownButton("Cookie");
+ assertEquals("Success! Priority lowered", flowsPage.getSuccessMessage());
+
+ flowsPage.table().clickLevelUpButton("Test Copy Of Browser Forms");
+ assertEquals("Success! Priority raised", flowsPage.getSuccessMessage());
+
+ flowsPage.table().clickLevelUpButton("OTP Forms");
+ assertEquals("Success! Priority raised", flowsPage.getSuccessMessage());
+
+ //rest:check if navigation was changed properly
+ }
+
+ @Test
+ public void requirementTest() {
+ //rest: add or copy flow to test navigation (browser), add reset, password
+
+ flowsPage.table().changeRequirement("Cookie", FlowsTable.RequirementOption.DISABLED);
+ flowsPage.table().changeRequirement("Kerberos", FlowsTable.RequirementOption.ALTERNATIVE);
+ flowsPage.table().changeRequirement("Copy Of Browser Forms", FlowsTable.RequirementOption.REQUIRED);
+ flowsPage.table().changeRequirement("Reset Password", FlowsTable.RequirementOption.REQUIRED);
+
+ //rest:check
+ }
+
+ @Test
+ public void actionsTest() {
+ //rest: add or copy flow to test navigation (browser)
+
+ flowsPage.table().performAction("Kerberos", FlowsTable.Action.DELETE);
+ flowsPage.table().performAction("Copy Of Browser Forms", FlowsTable.Action.ADD_FLOW);
+
+ createFlowPage.form().setValues("nestedFlow", "", CreateFlowForm.FlowType.CLIENT);
+
+ //todo: perform all remaining actions
+
+ //rest: check
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
index 6c40d64..94e9b4b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/AbstractClientTest.java
@@ -56,7 +56,9 @@ public abstract class AbstractClientTest extends AbstractConsoleTest {
client.setClientId(clientId);
client.setEnabled(true);
client.setConsentRequired(false);
- client.setDirectGrantsOnly(false);
+ client.setStandardFlowEnabled(true);
+ client.setImplicitFlowEnabled(false);
+ client.setDirectAccessGrantsEnabled(true);
client.setProtocol(OIDC);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
index ce68cce..9f1fd62 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientSettingsTest.java
@@ -111,7 +111,9 @@ public class ClientSettingsTest extends AbstractClientTest {
assertEqualsStringAttributes(c1.getName(), c2.getName());
assertEqualsBooleanAttributes(c1.isEnabled(), c2.isEnabled());
assertEqualsBooleanAttributes(c1.isConsentRequired(), c2.isConsentRequired());
- assertEqualsBooleanAttributes(c1.isDirectGrantsOnly(), c2.isDirectGrantsOnly());
+ assertEqualsBooleanAttributes(c1.isStandardFlowEnabled(), c2.isStandardFlowEnabled());
+ assertEqualsBooleanAttributes(c1.isImplicitFlowEnabled(), c2.isImplicitFlowEnabled());
+ assertEqualsBooleanAttributes(c1.isDirectAccessGrantsEnabled(), c2.isDirectAccessGrantsEnabled());
assertEqualsStringAttributes(c1.getProtocol(), c2.getProtocol());
assertEqualsBooleanAttributes(c1.isBearerOnly(), c2.isBearerOnly());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
index b43bfad..5174acf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/events/LoginEventsTest.java
@@ -55,7 +55,7 @@ public class LoginEventsTest extends AbstractConsoleTest {
List<WebElement> resultList = loginEventsPage.table().rows();
- assertEquals(7, resultList.size());
+ assertEquals(8, resultList.size());
resultList.get(0).findElement(By.xpath("//td[text()='LOGIN']"));
resultList.get(0).findElement(By.xpath("//td[text()='User']/../td[text()='" + testUser.getId() + "']"));
resultList.get(0).findElement(By.xpath("//td[text()='Client']/../td[text()='security-admin-console']"));