keycloak-memoizeit
Changes
adapters/oidc/adapter-core/pom.xml 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/AuthenticatedActionsHandler.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProvider.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientCredentialsProviderUtils.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/ClientIdAndSecretCredentialsProvider.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authentication/JWTClientCredentialsProvider.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/BearerTokenRequestAuthenticator.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/AbstractKeycloakLoginModule.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/BearerTokenLoginModule.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/NodesRegistrationManagement.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/OIDCAuthenticationError.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/RefreshableKeycloakSecurityContext.java 0(+0 -0)
adapters/oidc/adapter-core/src/main/resources/META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider 0(+0 -0)
adapters/oidc/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-adapter/src/main/java/org/keycloak/adapters/jbossweb/KeycloakAuthenticatorValve.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-adapter-spi/src/main/java/org/keycloak/adapters/jbossweb/JBossWebPrincipalFactory.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-adapter-spi/src/test/java/org/keycloak/adapters/jbossweb/JBossWebPrincipalFactoryTest.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/CredentialAddHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/CredentialDefinition.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/CredentialReadWriteAttributeHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/CredentialRemoveHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigDeploymentProcessor.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java 2(+1 -1)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessor.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakDependencyProcessorAS7.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakExtension.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemAdd.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemDefinition.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakSubsystemParser.java 4(+2 -2)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/logging/KeycloakLogger.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/logging/KeycloakMessages.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/RealmAddHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/RealmDefinition.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/RealmRemoveHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/RealmWriteAttributeHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentAddHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentDefinition.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentRemoveHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SecureDeploymentWriteAttributeHandler.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/SharedAttributeDefinitons.java 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/main/resources/org/keycloak/subsystem/as7/LocalDescriptions.properties 0(+0 -0)
adapters/oidc/as7-eap6/as7-subsystem/src/test/java/org/keycloak/subsystem/as7/RealmDefinitionTestCase.java 0(+0 -0)
adapters/oidc/as7-eap6/pom.xml 0(+0 -0)
adapters/oidc/installed/pom.xml 0(+0 -0)
adapters/oidc/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java 0(+0 -0)
adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilter.java 0(+0 -0)
adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsBearerTokenFilterImpl.java 0(+0 -0)
adapters/oidc/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/OsgiJaxrsBearerTokenFilterImpl.java 0(+0 -0)
adapters/oidc/jetty/jetty8.1/pom.xml 0(+0 -0)
adapters/oidc/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 0(+0 -0)
adapters/oidc/jetty/jetty8.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 0(+0 -0)
adapters/oidc/jetty/jetty9.1/pom.xml 0(+0 -0)
adapters/oidc/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 0(+0 -0)
adapters/oidc/jetty/jetty9.1/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 0(+0 -0)
adapters/oidc/jetty/jetty9.2/pom.xml 0(+0 -0)
adapters/oidc/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/JettyAdapterSessionStore.java 0(+0 -0)
adapters/oidc/jetty/jetty9.2/src/main/java/org/keycloak/adapters/jetty/KeycloakJettyAuthenticator.java 0(+0 -0)
adapters/oidc/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyHttpFacade.java 0(+0 -0)
adapters/oidc/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/JettyUserSessionManagement.java 0(+0 -0)
adapters/oidc/jetty/jetty-adapter-spi/src/main/java/org/keycloak/adapters/jetty/spi/WrappingSessionHandler.java 0(+0 -0)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/AbstractKeycloakJettyAuthenticator.java 0(+0 -0)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/JettyCookieTokenStore.java 0(+0 -0)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/JettyRequestAuthenticator.java 0(+0 -0)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/JettySessionTokenStore.java 0(+0 -0)
adapters/oidc/jetty/jetty-core/src/main/java/org/keycloak/adapters/jetty/core/OIDCJettyHttpFacade.java 0(+0 -0)
adapters/oidc/jetty/pom.xml 0(+0 -0)
adapters/oidc/js/pom.xml 0(+0 -0)
adapters/oidc/osgi-adapter/pom.xml 0(+0 -0)
adapters/oidc/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java 0(+0 -0)
adapters/oidc/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/ServletReregistrationService.java 0(+0 -0)
adapters/oidc/pom.xml 32(+32 -0)
adapters/oidc/servlet-filter/pom.xml 0(+0 -0)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/FilterRequestAuthenticator.java 0(+0 -0)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/KeycloakOIDCFilter.java 0(+0 -0)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java 0(+0 -0)
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCServletHttpFacade.java 0(+0 -0)
adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/KeycloakDeploymentDelegateOAuthClient.java 0(+0 -0)
adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java 0(+0 -0)
adapters/oidc/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java 0(+0 -0)
adapters/oidc/servlet-oauth-client/src/test/java/org/keycloak/servlet/ServletOAuthClientBuilderTest.java 0(+0 -0)
adapters/oidc/spring-boot/pom.xml 0(+0 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfigResolver.java 0(+0 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootConfiguration.java 0(+0 -0)
adapters/oidc/spring-boot/src/main/java/org/keycloak/adapters/springboot/KeycloakSpringBootProperties.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/KeycloakRole.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/account/SimpleKeycloakAccount.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBean.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/HttpHeaderInspectingApiRequestMatcher.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPoint.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProvider.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandler.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticator.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactory.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/client/KeycloakRestTemplate.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/config/KeycloakWebSecurityConfigurerAdapter.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/package-info.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/SimpleHttpFacade.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequest.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponse.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilter.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcher.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/KeycloakPreAuthActionsFilter.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/filter/package-info.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakAuthenticationException.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/KeycloakSecurityComponents.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/HttpSessionManager.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/LocalSessionManagementStrategy.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/management/SessionManagementStrategy.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java 4(+4 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/registration/NodeManager.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/AdapterTokenStoreFactory.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/KeycloakAuthenticationToken.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactory.java 0(+0 -0)
adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStore.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/AdapterDeploymentContextFactoryBeanTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/HttpHeaderInspectingApiRequestMatcherTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java 67(+67 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java 66(+66 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakLogoutHandlerTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/SpringSecurityRequestAuthenticatorTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java 81(+81 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletRequestTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/facade/WrappedHttpServletResponseTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakAuthenticationProcessingFilterTest.java 0(+0 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java 98(+98 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java 48(+48 -0)
adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java 92(+92 -0)
adapters/oidc/tomcat/pom.xml 0(+0 -0)
adapters/oidc/tomcat/tomcat6/pom.xml 96(+96 -0)
adapters/oidc/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 58(+58 -0)
adapters/oidc/tomcat/tomcat7/pom.xml 104(+104 -0)
adapters/oidc/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 54(+54 -0)
adapters/oidc/tomcat/tomcat8/pom.xml 103(+103 -0)
adapters/oidc/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 74(+74 -0)
adapters/oidc/tomcat/tomcat-core/pom.xml 97(+97 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AbstractKeycloakAuthenticatorValve.java 0(+0 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java 53(+53 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java 32(+32 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java 121(+121 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java 91(+91 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java 168(+168 -0)
adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/OIDCCatalinaHttpFacade.java 0(+0 -0)
adapters/oidc/undertow/pom.xml 94(+94 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java 134(+134 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java 91(+91 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java 29(+29 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java 0(+0 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java 94(+94 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java 40(+40 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java 40(+40 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java 126(+126 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java 73(+73 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java 76(+76 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java 129(+129 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java 68(+68 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java 42(+42 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java 93(+93 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationManagementWrapper.java 27(+27 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java 60(+60 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java 25(+25 -0)
adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java 103(+103 -0)
adapters/oidc/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+1 -0)
adapters/oidc/wildfly/pom.xml 21(+21 -0)
adapters/oidc/wildfly/wf8-subsystem/pom.xml 115(+115 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java 47(+47 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java 61(+61 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java 50(+50 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java 42(+42 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java 131(+131 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java 191(+191 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java 69(+69 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java 41(+41 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java 85(+85 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java 63(+63 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java 45(+45 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java 0(+0 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java 66(+66 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java 87(+87 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java 41(+41 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java 54(+54 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java 61(+61 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java 130(+130 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java 41(+41 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java 59(+59 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java 228(+228 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java 45(+45 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java 34(+34 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+1 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties 72(+72 -0)
adapters/oidc/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml 0(+0 -0)
adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java 86(+86 -0)
adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java 99(+99 -0)
adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml 26(+26 -0)
adapters/oidc/wildfly/wildfly-adapter/pom.xml 108(+108 -0)
adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/SecurityInfoHelper.java 116(+116 -0)
adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java 35(+35 -0)
adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java 25(+25 -0)
adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java 136(+136 -0)
adapters/oidc/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+1 -0)
adapters/oidc/wildfly/wildfly-subsystem/pom.xml 105(+105 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java 43(+43 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java 61(+61 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java 50(+50 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java 42(+42 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java 131(+131 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java 191(+191 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java 69(+69 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java 41(+41 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java 83(+83 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java 59(+59 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java 45(+45 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java 295(+295 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java 62(+62 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java 87(+87 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java 41(+41 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java 54(+54 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java 57(+57 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java 130(+130 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java 41(+41 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java 59(+59 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java 228(+228 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakLogger.java 45(+45 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakMessages.java 34(+34 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+1 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties 72(+72 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd 104(+104 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml 0(+0 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java 86(+86 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java 110(+110 -0)
adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml 26(+26 -0)
adapters/pom.xml 11(+5 -6)
adapters/saml/as7-eap6/adapter/pom.xml 97(+97 -0)
adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/SamlAuthenticatorValve.java 57(+57 -0)
adapters/saml/as7-eap6/pom.xml 20(+20 -0)
adapters/saml/as7-eap6/subsystem/pom.xml 115(+115 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java 60(+60 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java 120(+120 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java 41(+41 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java 106(+106 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java 41(+41 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java 149(+149 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java 67(+67 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessorAS7.java 19(+19 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java 79(+79 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java 61(+61 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java 46(+46 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemParser.java 0(+0 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java 122(+122 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java 36(+36 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java 73(+73 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java 52(+52 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java 49(+49 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java 34(+34 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java 42(+42 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java 44(+44 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java 43(+43 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java 125(+125 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java 84(+84 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java 68(+68 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java 534(+534 -0)
adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java 66(+66 -0)
adapters/saml/as7-eap6/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+1 -0)
adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties 63(+63 -0)
adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+268 -0)
adapters/saml/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml 49(+49 -0)
adapters/saml/core/pom.xml 35(+23 -12)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java 21(+21 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java 56(+56 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java 227(+227 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java 102(+102 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java 46(+46 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java 60(+60 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java 128(+128 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ResourceLoader.java 0(+0 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java 156(+156 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java 0(+0 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java 146(+146 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java 13(+13 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java 37(+37 -0)
adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java 111(+111 -0)
adapters/saml/jetty/jetty8.1/pom.xml 126(+126 -0)
adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 94(+94 -0)
adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 44(+44 -0)
adapters/saml/jetty/jetty9.1/pom.xml 141(+141 -0)
adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 94(+94 -0)
adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 44(+44 -0)
adapters/saml/jetty/jetty9.2/pom.xml 141(+141 -0)
adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 95(+95 -0)
adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 42(+42 -0)
adapters/saml/jetty/jetty-core/pom.xml 131(+131 -0)
adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java 304(+304 -0)
adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java 163(+163 -0)
adapters/saml/jetty/pom.xml 22(+22 -0)
adapters/saml/pom.xml 25(+25 -0)
adapters/saml/servlet-filter/pom.xml 69(+69 -0)
adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java 151(+151 -0)
adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java 165(+165 -0)
adapters/saml/tomcat/pom.xml 22(+22 -0)
adapters/saml/tomcat/tomcat6/pom.xml 76(+76 -0)
adapters/saml/tomcat/tomcat6/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 60(+60 -0)
adapters/saml/tomcat/tomcat7/pom.xml 84(+84 -0)
adapters/saml/tomcat/tomcat7/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 56(+56 -0)
adapters/saml/tomcat/tomcat8/pom.xml 83(+83 -0)
adapters/saml/tomcat/tomcat8/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 71(+71 -0)
adapters/saml/tomcat/tomcat-core/pom.xml 89(+89 -0)
adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java 244(+244 -0)
adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlAuthenticator.java 18(+18 -0)
adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java 214(+214 -0)
adapters/saml/undertow/pom.xml 87(+87 -0)
adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java 147(+147 -0)
adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java 201(+201 -0)
adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java 71(+71 -0)
adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java 213(+213 -0)
adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java 42(+42 -0)
adapters/saml/wildfly/pom.xml 20(+20 -0)
adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/SecurityInfoHelper.java 116(+116 -0)
adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlAuthMech.java 25(+25 -0)
adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlExtension.java 18(+18 -0)
adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlSessionStore.java 36(+36 -0)
adapters/saml/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+1 -0)
adapters/saml/wildfly/wildfly-subsystem/pom.xml 105(+105 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java 56(+56 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java 120(+120 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java 37(+37 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java 106(+106 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java 37(+37 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java 128(+128 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java 66(+66 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java 41(+41 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java 79(+79 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java 57(+57 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java 46(+46 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java 569(+569 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java 122(+122 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java 36(+36 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java 73(+73 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java 52(+52 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java 45(+45 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java 34(+34 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java 38(+38 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java 47(+47 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java 39(+39 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java 125(+125 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java 84(+84 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java 68(+68 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+1 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties 63(+63 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+268 -0)
adapters/saml/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml 7(+7 -0)
adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java 56(+56 -0)
adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml 50(+50 -0)
adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml 50(+50 -0)
adapters/spi/adapter-spi/pom.xml 0(+0 -0)
adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/InMemorySessionIdMapper.java 0(+0 -0)
adapters/spi/adapter-spi/src/main/java/org/keycloak/adapters/spi/UserSessionManagement.java 0(+0 -0)
adapters/spi/jboss-adapter-core/src/main/java/org/keycloak/adapters/jboss/KeycloakLoginModule.java 0(+0 -0)
adapters/spi/pom.xml 23(+23 -0)
adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java 0(+0 -0)
adapters/spi/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/ServletHttpFacade.java 0(+0 -0)
adapters/spi/tomcat-adapter-spi/pom.xml 69(+69 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaHttpFacade.java 0(+0 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java 77(+77 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java 30(+30 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/GenericPrincipalFactory.java 109(+109 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java 41(+41 -0)
adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java 50(+50 -0)
adapters/spi/undertow-adapter-spi/pom.xml 42(+27 -15)
adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java 81(+81 -0)
adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java 47(+47 -0)
adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java 213(+213 -0)
adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java 130(+130 -0)
broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 4(+0 -4)
broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory 1(+0 -1)
broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 3(+0 -3)
dependencies/server-all/pom.xml 38(+0 -38)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-oidc/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-broker-saml/main/module.xml 18(+0 -18)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-saml-protocol/main/module.xml 26(+0 -26)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml 7(+0 -7)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml 10(+1 -9)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-facebook/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-github/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-google/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-linkedin/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-stackoverflow/main/module.xml 22(+0 -22)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-social-twitter/main/module.xml 25(+0 -25)
forms/common-themes/pom.xml 30(+0 -30)
integration/pom.xml 18(+0 -18)
integration/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java 4(+0 -4)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java 67(+0 -67)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java 66(+0 -66)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java 81(+0 -81)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java 98(+0 -98)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java 48(+0 -48)
integration/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java 92(+0 -92)
integration/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 58(+0 -58)
integration/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 54(+0 -54)
integration/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java 74(+0 -74)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java 77(+0 -77)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java 30(+0 -30)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/GenericPrincipalFactory.java 109(+0 -109)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java 41(+0 -41)
integration/tomcat/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java 50(+0 -50)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java 53(+0 -53)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java 32(+0 -32)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java 121(+0 -121)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java 91(+0 -91)
integration/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java 168(+0 -168)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java 134(+0 -134)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java 91(+0 -91)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java 94(+0 -94)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java 40(+0 -40)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java 40(+0 -40)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java 126(+0 -126)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java 73(+0 -73)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java 76(+0 -76)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java 129(+0 -129)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java 68(+0 -68)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java 42(+0 -42)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java 93(+0 -93)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationManagementWrapper.java 27(+0 -27)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java 60(+0 -60)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java 25(+0 -25)
integration/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java 103(+0 -103)
integration/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+0 -1)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java 81(+0 -81)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java 47(+0 -47)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java 213(+0 -213)
integration/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java 130(+0 -130)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java 47(+0 -47)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java 61(+0 -61)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java 50(+0 -50)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java 42(+0 -42)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java 131(+0 -131)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java 191(+0 -191)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java 69(+0 -69)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java 41(+0 -41)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java 85(+0 -85)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java 63(+0 -63)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java 45(+0 -45)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java 66(+0 -66)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java 87(+0 -87)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java 41(+0 -41)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java 54(+0 -54)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java 61(+0 -61)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java 130(+0 -130)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java 41(+0 -41)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java 59(+0 -59)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java 228(+0 -228)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java 45(+0 -45)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java 34(+0 -34)
integration/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+0 -1)
integration/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties 72(+0 -72)
integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java 86(+0 -86)
integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java 99(+0 -99)
integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml 26(+0 -26)
integration/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/SecurityInfoHelper.java 116(+0 -116)
integration/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java 35(+0 -35)
integration/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java 25(+0 -25)
integration/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java 136(+0 -136)
integration/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+0 -1)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java 43(+0 -43)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java 61(+0 -61)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java 50(+0 -50)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java 42(+0 -42)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java 131(+0 -131)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java 191(+0 -191)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java 69(+0 -69)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java 41(+0 -41)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java 83(+0 -83)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java 59(+0 -59)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java 45(+0 -45)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java 295(+0 -295)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java 62(+0 -62)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java 87(+0 -87)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java 41(+0 -41)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java 54(+0 -54)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java 57(+0 -57)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java 130(+0 -130)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java 41(+0 -41)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java 59(+0 -59)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java 228(+0 -228)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakLogger.java 45(+0 -45)
integration/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakMessages.java 34(+0 -34)
integration/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+0 -1)
integration/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties 72(+0 -72)
integration/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd 104(+0 -104)
integration/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java 86(+0 -86)
integration/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java 110(+0 -110)
integration/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml 26(+0 -26)
pom.xml 54(+4 -50)
saml/client-adapter/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/SamlAuthenticatorValve.java 57(+0 -57)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java 60(+0 -60)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java 120(+0 -120)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java 41(+0 -41)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java 106(+0 -106)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java 41(+0 -41)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java 149(+0 -149)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java 67(+0 -67)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessorAS7.java 19(+0 -19)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java 79(+0 -79)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java 61(+0 -61)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java 46(+0 -46)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java 122(+0 -122)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java 36(+0 -36)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java 73(+0 -73)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java 52(+0 -52)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java 49(+0 -49)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java 34(+0 -34)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java 42(+0 -42)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java 44(+0 -44)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java 43(+0 -43)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java 125(+0 -125)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java 84(+0 -84)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java 68(+0 -68)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Util.java 42(+0 -42)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java 534(+0 -534)
saml/client-adapter/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java 66(+0 -66)
saml/client-adapter/as7-eap6/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+0 -1)
saml/client-adapter/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties 63(+0 -63)
saml/client-adapter/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+0 -268)
saml/client-adapter/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml 49(+0 -49)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java 91(+0 -91)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java 21(+0 -21)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java 56(+0 -56)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java 227(+0 -227)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java 102(+0 -102)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java 46(+0 -46)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java 60(+0 -60)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java 128(+0 -128)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java 156(+0 -156)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java 357(+0 -357)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java 146(+0 -146)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java 13(+0 -13)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java 37(+0 -37)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java 111(+0 -111)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java 49(+0 -49)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java 39(+0 -39)
saml/client-adapter/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java 19(+0 -19)
saml/client-adapter/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java 118(+0 -118)
saml/client-adapter/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 94(+0 -94)
saml/client-adapter/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 44(+0 -44)
saml/client-adapter/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 94(+0 -94)
saml/client-adapter/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 44(+0 -44)
saml/client-adapter/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java 95(+0 -95)
saml/client-adapter/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java 42(+0 -42)
saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java 304(+0 -304)
saml/client-adapter/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java 163(+0 -163)
saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java 151(+0 -151)
saml/client-adapter/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java 165(+0 -165)
saml/client-adapter/tomcat/tomcat6/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 60(+0 -60)
saml/client-adapter/tomcat/tomcat7/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 56(+0 -56)
saml/client-adapter/tomcat/tomcat8/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java 71(+0 -71)
saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java 244(+0 -244)
saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlAuthenticator.java 18(+0 -18)
saml/client-adapter/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java 214(+0 -214)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java 147(+0 -147)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java 201(+0 -201)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java 71(+0 -71)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java 213(+0 -213)
saml/client-adapter/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java 42(+0 -42)
saml/client-adapter/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/SecurityInfoHelper.java 116(+0 -116)
saml/client-adapter/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlAuthMech.java 25(+0 -25)
saml/client-adapter/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlExtension.java 18(+0 -18)
saml/client-adapter/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlSessionStore.java 36(+0 -36)
saml/client-adapter/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension 1(+0 -1)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java 56(+0 -56)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java 120(+0 -120)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java 37(+0 -37)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java 106(+0 -106)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java 37(+0 -37)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java 128(+0 -128)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java 66(+0 -66)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java 41(+0 -41)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java 79(+0 -79)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java 57(+0 -57)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java 46(+0 -46)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java 569(+0 -569)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java 122(+0 -122)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java 36(+0 -36)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java 73(+0 -73)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java 52(+0 -52)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java 45(+0 -45)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java 34(+0 -34)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java 38(+0 -38)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java 47(+0 -47)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java 39(+0 -39)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java 125(+0 -125)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java 84(+0 -84)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java 68(+0 -68)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+0 -1)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties 63(+0 -63)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd 268(+0 -268)
saml/client-adapter/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml 7(+0 -7)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java 56(+0 -56)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml 50(+0 -50)
saml/client-adapter/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml 50(+0 -50)
saml/pom.xml 2(+0 -2)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java 35(+0 -35)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java 38(+0 -38)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java 167(+0 -167)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java 160(+0 -160)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java 117(+0 -117)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java 120(+0 -120)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java 93(+0 -93)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java 76(+0 -76)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AbstractSAMLProtocolMapper.java 39(+0 -39)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java 94(+0 -94)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java 151(+0 -151)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java 79(+0 -79)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java 172(+0 -172)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java 128(+0 -128)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java 17(+0 -17)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java 12(+0 -12)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLLoginResponseMapper.java 17(+0 -17)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java 17(+0 -17)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleNameMapper.java 12(+0 -12)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java 81(+0 -81)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java 79(+0 -79)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/mappers/UserSessionNoteStatementMapper.java 67(+0 -67)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java 171(+0 -171)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java 141(+0 -141)
saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java 65(+0 -65)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 1(+0 -1)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory 1(+0 -1)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider 4(+0 -4)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory 1(+0 -1)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper 10(+0 -10)
saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory 1(+0 -1)
services/pom.xml 9(+9 -0)
services/src/main/java/org/keycloak/broker/oidc/mappers/AbstractJsonUserAttributeMapper.java 0(+0 -0)
services/src/main/java/org/keycloak/broker/oidc/mappers/ExternalKeycloakRoleToRoleMapper.java 0(+0 -0)
services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java 35(+35 -0)
services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java 38(+38 -0)
services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java 167(+167 -0)
services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java 160(+160 -0)
services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java 117(+117 -0)
services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java 120(+120 -0)
services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java 93(+93 -0)
services/src/main/java/org/keycloak/protocol/saml/mappers/AbstractSAMLProtocolMapper.java 39(+39 -0)
services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java 17(+17 -0)
services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java 81(+81 -0)
services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java 79(+79 -0)
services/src/main/java/org/keycloak/protocol/saml/mappers/UserSessionNoteStatementMapper.java 67(+67 -0)
services/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java 171(+171 -0)
services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java 140(+140 -0)
services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java 220(+220 -0)
services/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java 40(+40 -0)
services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java 47(+47 -0)
services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowUserAttributeMapper.java 29(+29 -0)
services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 3(+2 -1)
services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory 3(+2 -1)
services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 14(+13 -1)
services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 6(+6 -0)
services/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory 3(+2 -1)
services/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider 5(+5 -0)
services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory 3(+2 -1)
services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java 6(+4 -2)
services/src/test/java/org/keycloak/test/broker/oidc/mappers/AbstractJsonUserAttributeMapperTest.java 5(+3 -2)
social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java 46(+0 -46)
social/facebook/src/main/java/org/keycloak/social/facebook/FacebookUserAttributeMapper.java 29(+0 -29)
social/facebook/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+0 -1)
social/facebook/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+0 -1)
social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java 46(+0 -46)
social/github/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+0 -1)
social/github/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+0 -1)
social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java 46(+0 -46)
social/google/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+0 -1)
social/google/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+0 -1)
social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java 116(+0 -116)
social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java 47(+0 -47)
social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java 29(+0 -29)
social/linkedin/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+0 -1)
social/linkedin/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+0 -1)
social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java 220(+0 -220)
social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java 40(+0 -40)
social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java 47(+0 -47)
social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowUserAttributeMapper.java 29(+0 -29)
social/stackoverflow/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper 1(+0 -1)
social/stackoverflow/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory 1(+0 -1)
Details
adapters/oidc/pom.xml 32(+32 -0)
diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml
new file mode 100755
index 0000000..cfd8b8a
--- /dev/null
+++ b/adapters/oidc/pom.xml
@@ -0,0 +1,32 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak OIDC Client Adapter Modules</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-oidc-client-adapter-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>adapter-core</module>
+ <module>as7-eap6</module>
+ <module>installed</module>
+ <module>jaxrs-oauth-client</module>
+ <module>jetty</module>
+ <module>js</module>
+ <module>osgi-adapter</module>
+ <module>servlet-filter</module>
+ <module>servlet-oauth-client</module>
+ <module>spring-boot</module>
+ <module>spring-security</module>
+ <module>tomcat</module>
+ <module>undertow</module>
+ <module>wildfly</module>
+ </modules>
+</project>
diff --git a/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java
new file mode 100644
index 0000000..4df6bc3
--- /dev/null
+++ b/adapters/oidc/spring-security/src/main/java/org/keycloak/adapters/springsecurity/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * Provides a Keycloak adapter for Spring Security.
+ */
+package org.keycloak.adapters.springsecurity;
\ No newline at end of file
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java
new file mode 100644
index 0000000..f026015
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationEntryPointTest.java
@@ -0,0 +1,67 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.apache.http.HttpHeaders;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.http.HttpStatus;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.junit.Assert.*;
+
+/**
+ * Keycloak authentication entry point tests.
+ */
+public class KeycloakAuthenticationEntryPointTest {
+
+ private KeycloakAuthenticationEntryPoint authenticationEntryPoint;
+ private MockHttpServletRequest request;
+ private MockHttpServletResponse response;
+
+ @Before
+ public void setUp() throws Exception {
+ authenticationEntryPoint = new KeycloakAuthenticationEntryPoint();
+ request = new MockHttpServletRequest();
+ response = new MockHttpServletResponse();
+ }
+
+ @Test
+ public void testCommenceWithRedirect() throws Exception {
+ configureBrowserRequest();
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.FOUND.value(), response.getStatus());
+ assertEquals(KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
+ }
+
+ @Test
+ public void testCommenceWithRedirectNotRootContext() throws Exception {
+ configureBrowserRequest();
+ String contextPath = "/foo";
+ request.setContextPath(contextPath);
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.FOUND.value(), response.getStatus());
+ assertEquals(contextPath + KeycloakAuthenticationEntryPoint.DEFAULT_LOGIN_URI, response.getHeader("Location"));
+ }
+
+ @Test
+ public void testCommenceWithUnauthorizedWithAccept() throws Exception {
+ request.addHeader(HttpHeaders.ACCEPT, "application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.UNAUTHORIZED.value(), response.getStatus());
+ assertNotNull(response.getHeader(HttpHeaders.WWW_AUTHENTICATE));
+ }
+
+ @Test
+ public void testSetLoginUri() throws Exception {
+ configureBrowserRequest();
+ final String logoutUri = "/foo";
+ authenticationEntryPoint.setLoginUri(logoutUri);
+ authenticationEntryPoint.commence(request, response, null);
+ assertEquals(HttpStatus.FOUND.value(), response.getStatus());
+ assertEquals(logoutUri, response.getHeader("Location"));
+ }
+
+ private void configureBrowserRequest() {
+ request.addHeader(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
+ }
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java
new file mode 100644
index 0000000..1865bfd
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/authentication/KeycloakAuthenticationProviderTest.java
@@ -0,0 +1,66 @@
+package org.keycloak.adapters.springsecurity.authentication;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.spi.KeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
+import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
+import org.mockito.internal.util.collections.Sets;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import java.security.Principal;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Keycloak authentication provider tests.
+ */
+public class KeycloakAuthenticationProviderTest {
+ private KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
+ private KeycloakAuthenticationToken token;
+ private Set<String> roles = Sets.newSet("user", "admin");
+
+ @Before
+ public void setUp() throws Exception {
+ Principal principal = mock(Principal.class);
+ RefreshableKeycloakSecurityContext securityContext = mock(RefreshableKeycloakSecurityContext.class);
+ KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
+
+ token = new KeycloakAuthenticationToken(account);
+ }
+
+ @Test
+ public void testAuthenticate() throws Exception {
+ Authentication result = provider.authenticate(token);
+ assertNotNull(result);
+ assertEquals(roles, AuthorityUtils.authorityListToSet(result.getAuthorities()));
+ assertTrue(result.isAuthenticated());
+ assertNotNull(result.getPrincipal());
+ assertNotNull(result.getCredentials());
+ assertNotNull(result.getDetails());
+ }
+
+ @Test
+ public void testSupports() throws Exception {
+ assertTrue(provider.supports(KeycloakAuthenticationToken.class));
+ assertFalse(provider.supports(PreAuthenticatedAuthenticationToken.class));
+ }
+
+ @Test
+ public void testGrantedAuthoritiesMapper() throws Exception {
+ SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
+ grantedAuthorityMapper.setPrefix("ROLE_");
+ grantedAuthorityMapper.setConvertToUpperCase(true);
+ provider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
+
+ Authentication result = provider.authenticate(token);
+ assertEquals(Sets.newSet("ROLE_USER", "ROLE_ADMIN"),
+ AuthorityUtils.authorityListToSet(result.getAuthorities()));
+ }
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java
new file mode 100755
index 0000000..8796bf3
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/client/KeycloakClientRequestFactoryTest.java
@@ -0,0 +1,81 @@
+package org.keycloak.adapters.springsecurity.client;
+
+import org.apache.http.client.methods.HttpUriRequest;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.springsecurity.account.KeycloakRole;
+import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import java.util.Collections;
+import java.util.UUID;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Keycloak client request factory tests.
+ */
+public class KeycloakClientRequestFactoryTest {
+
+ @Spy
+ private KeycloakClientRequestFactory factory;
+
+ @Mock
+ private OidcKeycloakAccount account;
+
+ @Mock
+ private KeycloakAuthenticationToken keycloakAuthenticationToken;
+
+ @Mock
+ private KeycloakSecurityContext keycloakSecurityContext;
+
+ @Mock
+ private HttpUriRequest request;
+
+ private String bearerTokenString;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ bearerTokenString = UUID.randomUUID().toString();
+
+ SecurityContextHolder.getContext().setAuthentication(keycloakAuthenticationToken);
+ when(keycloakAuthenticationToken.getAccount()).thenReturn(account);
+ when(account.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext);
+ when(keycloakSecurityContext.getTokenString()).thenReturn(bearerTokenString);
+ }
+
+ @Test
+ public void testPostProcessHttpRequest() throws Exception {
+ factory.postProcessHttpRequest(request);
+ verify(factory).getKeycloakSecurityContext();
+ verify(request).setHeader(eq(KeycloakClientRequestFactory.AUTHORIZATION_HEADER), eq("Bearer " + bearerTokenString));
+ }
+
+ @Test
+ public void testGetKeycloakSecurityContext() throws Exception {
+ KeycloakSecurityContext context = factory.getKeycloakSecurityContext();
+ assertNotNull(context);
+ assertEquals(keycloakSecurityContext, context);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetKeycloakSecurityContextInvalidAuthentication() throws Exception {
+ SecurityContextHolder.getContext().setAuthentication(
+ new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("baz"))));
+ factory.getKeycloakSecurityContext();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testGetKeycloakSecurityContextNullAuthentication() throws Exception {
+ SecurityContextHolder.clearContext();
+ factory.getKeycloakSecurityContext();
+ }
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java
new file mode 100644
index 0000000..e729e53
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/filter/KeycloakCsrfRequestMatcherTest.java
@@ -0,0 +1,98 @@
+package org.keycloak.adapters.springsecurity.filter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.constants.AdapterConstants;
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import static org.junit.Assert.*;
+
+/**
+ * Keycloak CSRF request matcher tests.
+ */
+public class KeycloakCsrfRequestMatcherTest {
+
+ private static final String ROOT_CONTEXT_PATH = "";
+ private static final String SUB_CONTEXT_PATH = "/foo";
+
+ private KeycloakCsrfRequestMatcher matcher = new KeycloakCsrfRequestMatcher();
+
+ private MockHttpServletRequest request;
+
+ @Before
+ public void setUp() throws Exception {
+ request = new MockHttpServletRequest();
+ }
+
+ @Test
+ public void testMatchesMethodGet() throws Exception {
+ request.setMethod(HttpMethod.GET.name());
+ assertFalse(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesMethodPost() throws Exception {
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, "some/random/uri");
+ assertTrue(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, "some/random/uri");
+ assertTrue(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesKeycloakLogout() throws Exception {
+
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
+ assertFalse(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_LOGOUT);
+ assertFalse(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesKeycloakPushNotBefore() throws Exception {
+
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
+ assertFalse(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_PUSH_NOT_BEFORE);
+ assertFalse(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesKeycloakQueryBearerToken() throws Exception {
+
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
+ assertFalse(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_QUERY_BEARER_TOKEN);
+ assertFalse(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesKeycloakTestAvailable() throws Exception {
+
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
+ assertFalse(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_TEST_AVAILABLE);
+ assertFalse(matcher.matches(request));
+ }
+
+ @Test
+ public void testMatchesKeycloakVersion() throws Exception {
+
+ prepareRequest(HttpMethod.POST, ROOT_CONTEXT_PATH, AdapterConstants.K_VERSION);
+ assertFalse(matcher.matches(request));
+
+ prepareRequest(HttpMethod.POST, SUB_CONTEXT_PATH, AdapterConstants.K_VERSION);
+ assertFalse(matcher.matches(request));
+ }
+
+ private void prepareRequest(HttpMethod method, String contextPath, String uri) {
+ request.setMethod(method.name());
+ request.setContextPath(contextPath);
+ request.setRequestURI(contextPath + "/" + uri);
+ }
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
new file mode 100755
index 0000000..c0f27e1
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityAdapterTokenStoreFactoryTest.java
@@ -0,0 +1,48 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import javax.servlet.http.HttpServletRequest;
+
+import static org.junit.Assert.*;
+
+/**
+ * Spring Security adapter token store factory tests.
+ */
+public class SpringSecurityAdapterTokenStoreFactoryTest {
+
+ private AdapterTokenStoreFactory factory = new SpringSecurityAdapterTokenStoreFactory();
+
+ @Mock
+ private KeycloakDeployment deployment;
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testCreateAdapterTokenStore() throws Exception {
+ AdapterSessionStore store = factory.createAdapterTokenStore(deployment, request);
+ assertNotNull(store);
+ assertTrue(store instanceof SpringSecurityTokenStore);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateAdapterTokenStoreNullDeployment() throws Exception {
+ factory.createAdapterTokenStore(null, request);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCreateAdapterTokenStoreNullRequest() throws Exception {
+ factory.createAdapterTokenStore(deployment, null);
+ }
+}
diff --git a/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java
new file mode 100755
index 0000000..b1624aa
--- /dev/null
+++ b/adapters/oidc/spring-security/src/test/java/org/keycloak/adapters/springsecurity/token/SpringSecurityTokenStoreTest.java
@@ -0,0 +1,92 @@
+package org.keycloak.adapters.springsecurity.token;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.springsecurity.account.KeycloakRole;
+import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpSession;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+
+import java.security.Principal;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * Spring Security token store tests.
+ */
+public class SpringSecurityTokenStoreTest {
+
+ private SpringSecurityTokenStore store;
+
+ @Mock
+ private KeycloakDeployment deployment;
+
+ @Mock
+ private Principal principal;
+
+ @Mock
+ private RequestAuthenticator requestAuthenticator;
+
+ @Mock
+ private RefreshableKeycloakSecurityContext keycloakSecurityContext;
+
+ private MockHttpServletRequest request;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ request = new MockHttpServletRequest();
+ store = new SpringSecurityTokenStore(deployment, request);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ SecurityContextHolder.clearContext();
+ }
+
+ @Test
+ public void testIsCached() throws Exception {
+ Authentication authentication = new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("ROLE_FOO")));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ assertFalse(store.isCached(requestAuthenticator));
+ }
+
+ @Test
+ public void testSaveAccountInfo() throws Exception {
+ OidcKeycloakAccount account = new SimpleKeycloakAccount(principal, Collections.singleton("FOO"), keycloakSecurityContext);
+ Authentication authentication;
+
+ store.saveAccountInfo(account);
+ authentication = SecurityContextHolder.getContext().getAuthentication();
+
+ assertNotNull(authentication);
+ assertTrue(authentication instanceof KeycloakAuthenticationToken);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testSaveAccountInfoInvalidAuthenticationType() throws Exception {
+ OidcKeycloakAccount account = new SimpleKeycloakAccount(principal, Collections.singleton("FOO"), keycloakSecurityContext);
+ Authentication authentication = new PreAuthenticatedAuthenticationToken("foo", "bar", Collections.singleton(new KeycloakRole("ROLE_FOO")));
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ store.saveAccountInfo(account);
+ }
+
+ @Test
+ public void testLogout() throws Exception {
+ MockHttpSession session = (MockHttpSession) request.getSession(true);
+ assertFalse(session.isInvalid());
+ store.logout();
+ assertTrue(session.isInvalid());
+ }
+}
adapters/oidc/tomcat/tomcat6/pom.xml 96(+96 -0)
diff --git a/adapters/oidc/tomcat/tomcat6/pom.xml b/adapters/oidc/tomcat/tomcat6/pom.xml
new file mode 100755
index 0000000..d4d4612
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat6/pom.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat6-adapter</artifactId>
+ <name>Keycloak Tomcat 6 Integration</name>
+ <properties>
+ <tomcat.version>6.0.41</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat-core-adapter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/oidc/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
new file mode 100755
index 0000000..2e30429
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat6/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -0,0 +1,58 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
+ @Override
+ public boolean authenticate(Request request, Response response, LoginConfig config) throws java.io.IOException {
+ return authenticateInternal(request, response, config);
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ forwardToErrorPage(request, (Response)response, config);
+ return true;
+ }
+
+
+ @Override
+ public void start() throws LifecycleException {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ super.start();
+ }
+
+ public void logout(Request request) throws ServletException {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(null, userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/oidc/tomcat/tomcat7/pom.xml 104(+104 -0)
diff --git a/adapters/oidc/tomcat/tomcat7/pom.xml b/adapters/oidc/tomcat/tomcat7/pom.xml
new file mode 100755
index 0000000..a37271a
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat7/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat7-adapter</artifactId>
+ <name>Keycloak Tomcat 7 Integration</name>
+ <properties>
+ <!--<tomcat.version>8.0.14</tomcat.version>-->
+ <tomcat.version>7.0.52</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat-core-adapter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/oidc/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
new file mode 100755
index 0000000..fa94c34
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat7/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -0,0 +1,54 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
+ public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
+ return authenticateInternal(request, response, config);
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ forwardToErrorPage(request, (Response)response, config);
+ return true;
+ }
+
+
+ protected void initInternal() {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ }
+
+ public void logout(Request request) throws ServletException {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/oidc/tomcat/tomcat8/pom.xml 103(+103 -0)
diff --git a/adapters/oidc/tomcat/tomcat8/pom.xml b/adapters/oidc/tomcat/tomcat8/pom.xml
new file mode 100755
index 0000000..45f71db
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat8/pom.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat8-adapter</artifactId>
+ <name>Keycloak Tomcat 8 Integration</name>
+ <properties>
+ <tomcat.version>8.0.14</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat-core-adapter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/oidc/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java b/adapters/oidc/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
new file mode 100755
index 0000000..eea4f03
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat8/src/main/java/org/keycloak/adapters/tomcat/KeycloakAuthenticatorValve.java
@@ -0,0 +1,74 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
+ public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
+ return authenticateInternal(request, response, request.getContext().getLoginConfig());
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ // had to do this to get around compiler/IDE issues :(
+ try {
+ Method method = null;
+ /*
+ for (Method m : getClass().getDeclaredMethods()) {
+ if (m.getName().equals("forwardToErrorPage")) {
+ method = m;
+ break;
+ }
+ }
+ */
+ method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, HttpServletResponse.class, LoginConfig.class);
+ method.setAccessible(true);
+ method.invoke(this, request, response, config);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ protected void initInternal() {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ }
+
+ public void logout(Request request) {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/oidc/tomcat/tomcat-core/pom.xml 97(+97 -0)
diff --git a/adapters/oidc/tomcat/tomcat-core/pom.xml b/adapters/oidc/tomcat/tomcat-core/pom.xml
new file mode 100755
index 0000000..5264e2c
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat-core-adapter</artifactId>
+ <name>Keycloak Tomcat Core Integration</name>
+ <properties>
+ <!-- <tomcat.version>8.0.14</tomcat.version> -->
+ <!-- <tomcat.version>7.0.52</tomcat.version> -->
+ <tomcat.version>6.0.41</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <!--
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java
new file mode 100755
index 0000000..6b42b85
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/AuthenticatedActionsValve.java
@@ -0,0 +1,53 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Valve;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.valves.ValveBase;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+
+/**
+ * Pre-installed actions that must be authenticated
+ * <p/>
+ * Actions include:
+ * <p/>
+ * CORS Origin Check and Response headers
+ * k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AuthenticatedActionsValve extends ValveBase {
+ private static final Logger log = Logger.getLogger(AuthenticatedActionsValve.class);
+ protected AdapterDeploymentContext deploymentContext;
+
+ public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
+ this.deploymentContext = deploymentContext;
+ if (next == null) throw new RuntimeException("Next valve is null!!!");
+ setNext(next);
+ setContainer(container);
+ }
+
+
+ @Override
+ public void invoke(Request request, Response response) throws IOException, ServletException {
+ log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
+ CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment != null && deployment.isConfigured()) {
+ AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, new OIDCCatalinaHttpFacade(request, response));
+ if (handler.handledRequest()) {
+ return;
+ }
+
+ }
+ getNext().invoke(request, response);
+ }
+}
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
new file mode 100755
index 0000000..d4edf2f
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaAdapterSessionStore.java
@@ -0,0 +1,32 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaAdapterSessionStore implements AdapterSessionStore {
+ protected Request request;
+ protected AbstractKeycloakAuthenticatorValve valve;
+
+ public CatalinaAdapterSessionStore(Request request, AbstractKeycloakAuthenticatorValve valve) {
+ this.request = request;
+ this.valve = valve;
+ }
+
+ public void saveRequest() {
+ try {
+ valve.keycloakSaveRequest(request);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public boolean restoreRequest() {
+ return valve.keycloakRestoreRequest(request);
+ }
+}
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java
new file mode 100755
index 0000000..471e558
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaCookieTokenStore.java
@@ -0,0 +1,121 @@
+package org.keycloak.adapters.tomcat;
+
+import java.util.Set;
+import java.util.logging.Logger;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaCookieTokenStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger(""+CatalinaCookieTokenStore.class);
+
+ private Request request;
+ private HttpFacade facade;
+ private KeycloakDeployment deployment;
+ private GenericPrincipalFactory principalFactory;
+
+ private KeycloakPrincipal<RefreshableKeycloakSecurityContext> authenticatedPrincipal;
+
+ public CatalinaCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment, GenericPrincipalFactory principalFactory) {
+ this.request = request;
+ this.facade = facade;
+ this.deployment = deployment;
+ this.principalFactory = principalFactory;
+ }
+
+
+ @Override
+ public void checkCurrentToken() {
+ this.authenticatedPrincipal = checkPrincipalFromCookie();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ // Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
+ if (authenticatedPrincipal != null) {
+ log.fine("remote logged in already. Establish state from cookie");
+ RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
+
+ if (!securityContext.getRealm().equals(deployment.getRealm())) {
+ log.fine("Account from cookie is from a different realm than for the request.");
+ return false;
+ }
+
+ securityContext.setCurrentRequestInfo(deployment, this);
+ Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ GenericPrincipal principal = principalFactory.createPrincipal(request.getContext().getRealm(), authenticatedPrincipal, roles);
+
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+ }
+
+ @Override
+ public void logout() {
+ CookieTokenStore.removeCookie(facade);
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
+ CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+ }
+
+ @Override
+ public void saveRequest() {
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return false;
+ }
+
+ /**
+ * Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
+ *
+ * @return valid principal
+ */
+ protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
+ KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) {
+ log.fine("Account was not in cookie or was invalid");
+ return null;
+ }
+
+ RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
+
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return principal;
+
+ log.fine("Cleanup and expire cookie for user " + principal.getName() + " after failed refresh");
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ CookieTokenStore.removeCookie(facade);
+ return null;
+ }
+}
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java
new file mode 100755
index 0000000..0883c76
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaRequestAuthenticator.java
@@ -0,0 +1,91 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+import java.security.Principal;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaRequestAuthenticator extends RequestAuthenticator {
+ private static final Logger log = Logger.getLogger(""+CatalinaRequestAuthenticator.class);
+ protected Request request;
+ protected GenericPrincipalFactory principalFactory;
+
+ public CatalinaRequestAuthenticator(KeycloakDeployment deployment,
+ AdapterTokenStore tokenStore,
+ CatalinaHttpFacade facade,
+ Request request,
+ GenericPrincipalFactory principalFactory) {
+ super(facade, deployment, tokenStore, request.getConnector().getRedirectPort());
+ this.request = request;
+ this.principalFactory = principalFactory;
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
+ }
+
+ @Override
+ protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
+ final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
+ final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ OidcKeycloakAccount account = new OidcKeycloakAccount() {
+
+ @Override
+ public Principal getPrincipal() {
+ return skp;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ @Override
+ public KeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+
+ };
+
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ this.tokenStore.saveAccountInfo(account);
+ }
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
+ RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
+ Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+ if (log.isLoggable(Level.FINE)) {
+ log.fine("Completing bearer authentication. Bearer roles: " + roles);
+ }
+ Principal generalPrincipal = principalFactory.createPrincipal(request.getContext().getRealm(), principal, roles);
+ request.setUserPrincipal(generalPrincipal);
+ request.setAuthType(method);
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = request.getSession(create);
+ return session != null ? session.getId() : null;
+ }
+
+}
diff --git a/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
new file mode 100755
index 0000000..9d147fa
--- /dev/null
+++ b/adapters/oidc/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/tomcat/CatalinaSessionTokenStore.java
@@ -0,0 +1,168 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaSessionTokenStore extends CatalinaAdapterSessionStore implements AdapterTokenStore {
+
+ private static final Logger log = Logger.getLogger("" + CatalinaSessionTokenStore.class);
+
+ private KeycloakDeployment deployment;
+ private CatalinaUserSessionManagement sessionManagement;
+ protected GenericPrincipalFactory principalFactory;
+
+
+ public CatalinaSessionTokenStore(Request request, KeycloakDeployment deployment,
+ CatalinaUserSessionManagement sessionManagement,
+ GenericPrincipalFactory principalFactory,
+ AbstractKeycloakAuthenticatorValve valve) {
+ super(request, valve);
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ this.principalFactory = principalFactory;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ Session catalinaSession = request.getSessionInternal(false);
+ if (catalinaSession == null) return;
+ SerializableKeycloakAccount account = (SerializableKeycloakAccount) catalinaSession.getSession().getAttribute(SerializableKeycloakAccount.class.getName());
+ if (account == null) {
+ return;
+ }
+
+ RefreshableKeycloakSecurityContext session = account.getKeycloakSecurityContext();
+ if (session == null) return;
+
+ // just in case session got serialized
+ if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
+
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
+
+ // FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
+ // not be updated
+ boolean success = session.refreshExpiredToken(false);
+ if (success && session.isActive()) return;
+
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ log.fine("Cleanup and expire session " + catalinaSession.getId() + " after failed refresh");
+ request.setUserPrincipal(null);
+ request.setAuthType(null);
+ cleanSession(catalinaSession);
+ catalinaSession.expire();
+ }
+
+ protected void cleanSession(Session catalinaSession) {
+ catalinaSession.getSession().removeAttribute(OidcKeycloakAccount.class.getName());
+ catalinaSession.setPrincipal(null);
+ catalinaSession.setAuthType(null);
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ Session session = request.getSessionInternal(false);
+ if (session == null) return false;
+ SerializableKeycloakAccount account = (SerializableKeycloakAccount) session.getSession().getAttribute(SerializableKeycloakAccount.class.getName());
+ if (account == null) {
+ return false;
+ }
+
+ log.fine("remote logged in already. Establish state from session");
+
+ RefreshableKeycloakSecurityContext securityContext = account.getKeycloakSecurityContext();
+
+ if (!deployment.getRealm().equals(securityContext.getRealm())) {
+ log.fine("Account from cookie is from a different realm than for the request.");
+ cleanSession(session);
+ return false;
+ }
+
+ securityContext.setCurrentRequestInfo(deployment, this);
+ request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
+ GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+ // in clustered environment in JBossWeb, principal is not serialized or saved
+ if (principal == null) {
+ principal = principalFactory.createPrincipal(request.getContext().getRealm(), account.getPrincipal(), account.getRoles());
+ session.setPrincipal(principal);
+ session.setAuthType("KEYCLOAK");
+
+ }
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK");
+
+ restoreRequest();
+ return true;
+ }
+
+ public static class SerializableKeycloakAccount implements OidcKeycloakAccount, Serializable {
+ protected Set<String> roles;
+ protected Principal principal;
+ protected RefreshableKeycloakSecurityContext securityContext;
+
+ public SerializableKeycloakAccount(Set<String> roles, Principal principal, RefreshableKeycloakSecurityContext securityContext) {
+ this.roles = roles;
+ this.principal = principal;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public Principal getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ @Override
+ public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
+ return securityContext;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
+ Set<String> roles = account.getRoles();
+ GenericPrincipal principal = principalFactory.createPrincipal(request.getContext().getRealm(), account.getPrincipal(), roles);
+
+ SerializableKeycloakAccount sAccount = new SerializableKeycloakAccount(roles, account.getPrincipal(), securityContext);
+ Session session = request.getSessionInternal(true);
+ session.setPrincipal(principal);
+ session.setAuthType("KEYCLOAK");
+ session.getSession().setAttribute(SerializableKeycloakAccount.class.getName(), sAccount);
+ String username = securityContext.getToken().getSubject();
+ log.fine("userSessionManagement.login: " + username);
+ this.sessionManagement.login(session);
+ }
+
+ @Override
+ public void logout() {
+ Session session = request.getSessionInternal(false);
+ if (session != null) {
+ cleanSession(session);
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+
+}
adapters/oidc/undertow/pom.xml 94(+94 -0)
diff --git a/adapters/oidc/undertow/pom.xml b/adapters/oidc/undertow/pom.xml
new file mode 100755
index 0000000..f97c013
--- /dev/null
+++ b/adapters/oidc/undertow/pom.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ <name>Keycloak Undertow Integration</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
new file mode 100755
index 0000000..36e8aa4
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowKeycloakAuthMech.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.NotificationReceiver;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.api.SecurityNotification;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
+import io.undertow.util.StatusCodes;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
+
+/**
+ * Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public abstract class AbstractUndertowKeycloakAuthMech implements AuthenticationMechanism {
+ public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
+ protected AdapterDeploymentContext deploymentContext;
+ protected UndertowUserSessionManagement sessionManagement;
+ protected String errorPage;
+
+ public AbstractUndertowKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
+ this.deploymentContext = deploymentContext;
+ this.sessionManagement = sessionManagement;
+ this.errorPage = errorPage;
+ }
+
+ @Override
+ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+ if (challenge != null) {
+ UndertowHttpFacade facade = createFacade(exchange);
+ if (challenge.challenge(facade)) {
+ return new ChallengeResult(true, exchange.getResponseCode());
+ }
+ }
+ return new ChallengeResult(false);
+ }
+
+ public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+ return new OIDCUndertowHttpFacade(exchange);
+ }
+
+ protected Integer servePage(final HttpServerExchange exchange, final String location) {
+ sendRedirect(exchange, location);
+ return StatusCodes.TEMPORARY_REDIRECT;
+ }
+
+ static void sendRedirect(final HttpServerExchange exchange, final String location) {
+ // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
+ String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
+ exchange.getResponseHeaders().put(Headers.LOCATION, loc);
+ }
+
+
+
+ protected void registerNotifications(final SecurityContext securityContext) {
+
+ final NotificationReceiver logoutReceiver = new NotificationReceiver() {
+ @Override
+ public void handleNotification(SecurityNotification notification) {
+ if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT) return;
+
+ HttpServerExchange exchange = notification.getExchange();
+ UndertowHttpFacade facade = createFacade(exchange);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ KeycloakSecurityContext ksc = exchange.getAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY);
+ if (ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
+ tokenStore.logout();
+ }
+ };
+
+ securityContext.registerNotificationReceiver(logoutReceiver);
+ }
+
+ /**
+ * Call this inside your authenticate method.
+ */
+ protected AuthenticationMechanismOutcome keycloakAuthenticate(HttpServerExchange exchange, SecurityContext securityContext, RequestAuthenticator authenticator) {
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ registerNotifications(securityContext);
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, challenge);
+ }
+
+ if (outcome == AuthOutcome.FAILED) {
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ return new UndertowSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+ } else {
+ return new UndertowCookieTokenStore(facade, deployment, securityContext);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
new file mode 100755
index 0000000..18c846c
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/AbstractUndertowRequestAuthenticator.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.util.Sessions;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractUndertowRequestAuthenticator extends RequestAuthenticator {
+ protected SecurityContext securityContext;
+ protected HttpServerExchange exchange;
+
+
+ public AbstractUndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
+ SecurityContext securityContext, HttpServerExchange exchange,
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, tokenStore, sslRedirectPort);
+ this.securityContext = securityContext;
+ this.exchange = exchange;
+ }
+
+ protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ exchange.putAttachment(OIDCUndertowHttpFacade.KEYCLOAK_SECURITY_CONTEXT_KEY, account.getKeycloakSecurityContext());
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
+ }
+
+ @Override
+ protected void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+ KeycloakUndertowAccount account = createAccount(principal);
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ propagateKeycloakContext(account);
+ tokenStore.saveAccountInfo(account);
+ }
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
+ KeycloakUndertowAccount account = createAccount(principal);
+ securityContext.authenticationComplete(account, method, false);
+ propagateKeycloakContext(account);
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ if (create) {
+ Session session = Sessions.getOrCreateSession(exchange);
+ return session.getId();
+ } else {
+ Session session = Sessions.getSession(exchange);
+ return session != null ? session.getId() : null;
+ }
+ }
+
+ /**
+ * Subclasses need to be able to create their own version of the KeycloakUndertowAccount
+ * @return The account
+ */
+ protected abstract KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java
new file mode 100755
index 0000000..46805b0
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakChallenge.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface KeycloakChallenge {
+ public AuthenticationMechanism.ChallengeResult sendChallenge(HttpServerExchange httpServerExchange, SecurityContext securityContext);
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
new file mode 100755
index 0000000..c3a0184
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakUndertowAccount.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.idm.Account;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Set;
+
+/**
+* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @version $Revision: 1 $
+*/
+public class KeycloakUndertowAccount implements Account, Serializable, OidcKeycloakAccount {
+ protected static Logger log = Logger.getLogger(KeycloakUndertowAccount.class);
+ protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
+ protected Set<String> accountRoles;
+
+ public KeycloakUndertowAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+ this.principal = principal;
+ setRoles(principal.getKeycloakSecurityContext());
+ }
+
+ protected void setRoles(RefreshableKeycloakSecurityContext session) {
+ Set<String> roles = AdapterUtils.getRolesFromSecurityContext(session);
+ this.accountRoles = roles;
+ }
+
+ @Override
+ public Principal getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return accountRoles;
+ }
+
+ @Override
+ public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
+ return principal.getKeycloakSecurityContext();
+ }
+
+ public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+ principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
+ }
+
+ // Check if accessToken is active and try to refresh if it's not
+ public boolean checkActive() {
+ // this object may have been serialized, so we need to reset realm config/metadata
+ RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
+ log.debug("session is active");
+ return true;
+ }
+
+ log.debug("session is not active or refresh is enforced. Try refresh");
+ boolean success = session.refreshExpiredToken(false);
+ if (!success || !session.isActive()) {
+ log.debug("session is not active return with failure");
+
+ return false;
+ }
+ log.debug("refresh succeeded");
+
+ setRoles(session);
+ return true;
+ }
+
+
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java
new file mode 100755
index 0000000..ddf0f41
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCServletUndertowHttpFacade.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCServletUndertowHttpFacade extends ServletHttpFacade implements OIDCHttpFacade {
+ public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
+ public OIDCServletUndertowHttpFacade(HttpServerExchange exchange) {
+ super(exchange);
+ }
+
+ @Override
+ public KeycloakSecurityContext getSecurityContext() {
+ return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+ }
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java
new file mode 100755
index 0000000..9063399
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/OIDCUndertowHttpFacade.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OIDCUndertowHttpFacade extends UndertowHttpFacade implements OIDCHttpFacade {
+ public static final AttachmentKey<KeycloakSecurityContext> KEYCLOAK_SECURITY_CONTEXT_KEY = AttachmentKey.create(KeycloakSecurityContext.class);
+
+ public OIDCUndertowHttpFacade(HttpServerExchange exchange) {
+ super(exchange);
+ }
+
+ @Override
+ public KeycloakSecurityContext getSecurityContext() {
+ return exchange.getAttachment(KEYCLOAK_SECURITY_CONTEXT_KEY);
+ }
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java
new file mode 100755
index 0000000..bd56cde
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/SavedRequest.java
@@ -0,0 +1,131 @@
+package org.keycloak.adapters.undertow;
+import io.undertow.UndertowLogger;
+import io.undertow.UndertowOptions;
+import io.undertow.server.Connectors;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.servlet.spec.HttpSessionImpl;
+import io.undertow.util.HeaderMap;
+import io.undertow.util.HeaderValues;
+import io.undertow.util.Headers;
+import io.undertow.util.HttpString;
+import io.undertow.util.ImmediatePooled;
+
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.util.Iterator;
+
+/**
+ * Saved servlet request.
+ *
+ * Note bill burke: I had to fork this because Undertow was automatically restoring the request before the code could be processed and redirected.
+ *
+ * @author Stuart Douglas
+ */
+public class SavedRequest implements Serializable {
+
+ private static final String SESSION_KEY = SavedRequest.class.getName();
+
+ private final byte[] data;
+ private final int dataLength;
+ private final HttpString method;
+ private final String requestUri;
+ private final HeaderMap headerMap;
+
+ public SavedRequest(byte[] data, int dataLength, HttpString method, String requestUri, HeaderMap headerMap) {
+ this.data = data;
+ this.dataLength = dataLength;
+ this.method = method;
+ this.requestUri = requestUri;
+ this.headerMap = headerMap;
+ }
+
+ public static void trySaveRequest(final HttpServerExchange exchange) {
+ int maxSize = exchange.getConnection().getUndertowOptions().get(UndertowOptions.MAX_BUFFERED_REQUEST_SIZE, 16384);
+ if (maxSize > 0) {
+ //if this request has a body try and cache the response
+ if (!exchange.isRequestComplete()) {
+ final long requestContentLength = exchange.getRequestContentLength();
+ if (requestContentLength > maxSize) {
+ UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI());
+ return;//failed to save the request, we just return
+ }
+ //TODO: we should really be used pooled buffers
+ //TODO: we should probably limit the number of saved requests at any given time
+ byte[] buffer = new byte[maxSize];
+ int read = 0;
+ int res = 0;
+ InputStream in = exchange.getInputStream();
+ try {
+ while ((res = in.read(buffer, read, buffer.length - read)) > 0) {
+ read += res;
+ if (read == maxSize) {
+ UndertowLogger.REQUEST_LOGGER.debugf("Request to %s was to large to save", exchange.getRequestURI());
+ return;//failed to save the request, we just return
+ }
+ }
+ HeaderMap headers = new HeaderMap();
+ for(HeaderValues entry : exchange.getRequestHeaders()) {
+ if(entry.getHeaderName().equals(Headers.CONTENT_LENGTH) ||
+ entry.getHeaderName().equals(Headers.TRANSFER_ENCODING) ||
+ entry.getHeaderName().equals(Headers.CONNECTION)) {
+ continue;
+ }
+ headers.putAll(entry.getHeaderName(), entry);
+ }
+ SavedRequest request = new SavedRequest(buffer, read, exchange.getRequestMethod(), exchange.getRequestURI(), exchange.getRequestHeaders());
+ final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+ Session underlyingSession;
+ if(System.getSecurityManager() == null) {
+ underlyingSession = session.getSession();
+ } else {
+ underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session));
+ }
+ underlyingSession.setAttribute(SESSION_KEY, request);
+ } catch (IOException e) {
+ UndertowLogger.REQUEST_IO_LOGGER.ioException(e);
+ }
+ }
+ }
+ }
+
+ public static void tryRestoreRequest(final HttpServerExchange exchange, HttpSession session) {
+ if(session instanceof HttpSessionImpl) {
+
+ Session underlyingSession;
+ if(System.getSecurityManager() == null) {
+ underlyingSession = ((HttpSessionImpl) session).getSession();
+ } else {
+ underlyingSession = AccessController.doPrivileged(new HttpSessionImpl.UnwrapSessionAction(session));
+ }
+ SavedRequest request = (SavedRequest) underlyingSession.getAttribute(SESSION_KEY);
+ if(request != null) {
+ if(request.requestUri.equals(exchange.getRequestURI()) && exchange.isRequestComplete()) {
+ UndertowLogger.REQUEST_LOGGER.debugf("restoring request body for request to %s", request.requestUri);
+ exchange.setRequestMethod(request.method);
+ Connectors.ungetRequestBytes(exchange, new ImmediatePooled<ByteBuffer>(ByteBuffer.wrap(request.data, 0, request.dataLength)));
+ underlyingSession.removeAttribute(SESSION_KEY);
+ //clear the existing header map of everything except the connection header
+ //TODO: are there other headers we should preserve?
+ Iterator<HeaderValues> headerIterator = exchange.getRequestHeaders().iterator();
+ while (headerIterator.hasNext()) {
+ HeaderValues header = headerIterator.next();
+ if(!header.getHeaderName().equals(Headers.CONNECTION)) {
+ headerIterator.remove();
+ }
+ }
+ for(HeaderValues header : request.headerMap) {
+ exchange.getRequestHeaders().putAll(header.getHeaderName(), header);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
new file mode 100755
index 0000000..b546e76
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletKeycloakAuthMech.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.api.ConfidentialPortManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.Headers;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.enums.TokenStore;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ * @version $Revision: 1 $
+ */
+public class ServletKeycloakAuthMech extends AbstractUndertowKeycloakAuthMech {
+ private static final Logger log = Logger.getLogger(ServletKeycloakAuthMech.class);
+
+ protected NodesRegistrationManagement nodesRegistrationManagement;
+ protected ConfidentialPortManager portManager;
+
+ public ServletKeycloakAuthMech(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement,
+ NodesRegistrationManagement nodesRegistrationManagement, ConfidentialPortManager portManager,
+ String errorPage) {
+ super(deploymentContext, userSessionManagement, errorPage);
+ this.nodesRegistrationManagement = nodesRegistrationManagement;
+ this.portManager = portManager;
+ }
+
+ @Override
+ protected Integer servePage(HttpServerExchange exchange, String location) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ ServletRequest req = servletRequestContext.getServletRequest();
+ ServletResponse resp = servletRequestContext.getServletResponse();
+ RequestDispatcher disp = req.getRequestDispatcher(location);
+ //make sure the login page is never cached
+ exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
+ exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache");
+ exchange.getResponseHeaders().add(Headers.EXPIRES, "0");
+
+
+ try {
+ disp.forward(req, resp);
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+ @Override
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ UndertowHttpFacade facade = createFacade(exchange);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (!deployment.isConfigured()) {
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ nodesRegistrationManagement.tryRegister(deployment);
+
+ RequestAuthenticator authenticator = createRequestAuthenticator(deployment, exchange, securityContext, facade);
+
+ return keycloakAuthenticate(exchange, securityContext, authenticator);
+ }
+
+ protected RequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
+
+ int confidentialPort = getConfidentilPort(exchange);
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
+ return new ServletRequestAuthenticator(facade, deployment,
+ confidentialPort, securityContext, exchange, tokenStore);
+ }
+
+ protected int getConfidentilPort(HttpServerExchange exchange) {
+ int confidentialPort = 8443;
+ if (exchange.getRequestScheme().equalsIgnoreCase("HTTPS")) {
+ confidentialPort = exchange.getHostPort();
+ } else if (portManager != null) {
+ confidentialPort = portManager.getConfidentialPort(exchange);
+ }
+ return confidentialPort;
+ }
+
+ @Override
+ protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
+ if (deployment.getTokenStore() == TokenStore.SESSION) {
+ return new ServletSessionTokenStore(exchange, deployment, sessionManagement, securityContext);
+ } else {
+ return new UndertowCookieTokenStore(facade, deployment, securityContext);
+ }
+ }
+
+ @Override
+ public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+ return new OIDCServletUndertowHttpFacade(exchange);
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
new file mode 100755
index 0000000..05672db
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletPreAuthActionsHandler.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.PreAuthActionsHandler;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletPreAuthActionsHandler implements HttpHandler {
+
+ private static final Logger log = Logger.getLogger(ServletPreAuthActionsHandler.class);
+ protected HttpHandler next;
+ protected UndertowUserSessionManagement userSessionManagement;
+ protected AdapterDeploymentContext deploymentContext;
+
+ public static class Wrapper implements HandlerWrapper {
+ protected AdapterDeploymentContext deploymentContext;
+ protected UndertowUserSessionManagement userSessionManagement;
+
+
+ public Wrapper(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement) {
+ this.deploymentContext = deploymentContext;
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new ServletPreAuthActionsHandler(deploymentContext, userSessionManagement, handler);
+ }
+ }
+
+ protected ServletPreAuthActionsHandler(AdapterDeploymentContext deploymentContext,
+ UndertowUserSessionManagement userSessionManagement,
+ HttpHandler next) {
+ this.next = next;
+ this.deploymentContext = deploymentContext;
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ UndertowHttpFacade facade = new OIDCServletUndertowHttpFacade(exchange);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, servletRequestContext.getDeployment().getSessionManager());
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
+ if (handler.handleRequest()) return;
+ next.handleRequest(exchange);
+ }
+
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
new file mode 100755
index 0000000..881407d
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletRequestAuthenticator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ * @version $Revision: 1 $
+ */
+public class ServletRequestAuthenticator extends AbstractUndertowRequestAuthenticator {
+
+
+ public ServletRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
+ SecurityContext securityContext, HttpServerExchange exchange,
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
+ }
+
+ @Override
+ protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ super.propagateKeycloakContext(account);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ req.setAttribute(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
+ }
+
+ @Override
+ protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+ return new KeycloakUndertowAccount(principal);
+ }
+
+ @Override
+ protected String getHttpSessionId(boolean create) {
+ HttpSession session = getSession(create);
+ return session != null ? session.getId() : null;
+ }
+
+ protected HttpSession getSession(boolean create) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ return req.getSession(create);
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
new file mode 100755
index 0000000..5996589
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/ServletSessionTokenStore.java
@@ -0,0 +1,129 @@
+package org.keycloak.adapters.undertow;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in servlet HTTP session.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class ServletSessionTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(ServletSessionTokenStore.class);
+
+ private final HttpServerExchange exchange;
+ private final KeycloakDeployment deployment;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+
+ public ServletSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext) {
+ this.exchange = exchange;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ HttpSession session = getSession(false);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) {
+ log.debug("Account was not in session, returning null");
+ return false;
+ }
+
+ if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+ log.debug("Account in session belongs to a different realm than for this request.");
+ return false;
+ }
+
+ account.setCurrentRequestInfo(deployment, this);
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ restoreRequest();
+ return true;
+ } else {
+ log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), null);
+ session.invalidate();
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSession session = getSession(true);
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+ }
+
+ @Override
+ public void logout() {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ req.removeAttribute(KeycloakUndertowAccount.class.getName());
+ req.removeAttribute(KeycloakSecurityContext.class.getName());
+ HttpSession session = req.getSession(false);
+ if (session == null) return;
+ try {
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount) session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) return;
+ session.removeAttribute(KeycloakSecurityContext.class.getName());
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ } catch (IllegalStateException ise) {
+ // Session may be already logged-out in case that app has adminUrl
+ log.debugf("Session %s logged-out already", session.getId());
+ }
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+
+ @Override
+ public void saveRequest() {
+ SavedRequest.trySaveRequest(exchange);
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ HttpSession session = getSession(false);
+ if (session == null) return false;
+ SavedRequest.tryRestoreRequest(exchange, session);
+ return false;
+ }
+
+ protected HttpSession getSession(boolean create) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpServletRequest req = (HttpServletRequest) servletRequestContext.getServletRequest();
+ return req.getSession(create);
+ }
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
new file mode 100755
index 0000000..4e7590f
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticatedActionsHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HandlerWrapper;
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+
+/**
+ * Bridge for authenticated Keycloak adapter actions
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ * @version $Revision: 1 $
+ */
+public class UndertowAuthenticatedActionsHandler implements HttpHandler {
+ private static final Logger log = Logger.getLogger(UndertowAuthenticatedActionsHandler.class);
+ protected AdapterDeploymentContext deploymentContext;
+ protected HttpHandler next;
+
+ public static class Wrapper implements HandlerWrapper {
+ protected AdapterDeploymentContext deploymentContext;
+
+ public Wrapper(AdapterDeploymentContext deploymentContext) {
+ this.deploymentContext = deploymentContext;
+ }
+
+ @Override
+ public HttpHandler wrap(HttpHandler handler) {
+ return new UndertowAuthenticatedActionsHandler(deploymentContext, handler);
+ }
+ }
+
+
+ public UndertowAuthenticatedActionsHandler(AdapterDeploymentContext deploymentContext, HttpHandler next) {
+ this.deploymentContext = deploymentContext;
+ this.next = next;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ OIDCUndertowHttpFacade facade = new OIDCUndertowHttpFacade(exchange);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment != null && deployment.isConfigured()) {
+ AuthenticatedActionsHandler handler = new AuthenticatedActionsHandler(deployment, facade);
+ if (handler.handledRequest()) return;
+ }
+ next.handleRequest(exchange);
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
new file mode 100755
index 0000000..88ba705
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowAuthenticationMechanism.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowAuthenticationMechanism extends AbstractUndertowKeycloakAuthMech {
+ protected NodesRegistrationManagement nodesRegistrationManagement;
+ protected int confidentialPort;
+
+ public UndertowAuthenticationMechanism(AdapterDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement,
+ NodesRegistrationManagement nodesRegistrationManagement, int confidentialPort, String errorPage) {
+ super(deploymentContext, sessionManagement, errorPage);
+ this.nodesRegistrationManagement = nodesRegistrationManagement;
+ this.confidentialPort = confidentialPort;
+ }
+
+ @Override
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ UndertowHttpFacade facade = createFacade(exchange);
+ KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (!deployment.isConfigured()) {
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ nodesRegistrationManagement.tryRegister(deployment);
+
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
+ RequestAuthenticator authenticator = new UndertowRequestAuthenticator(facade, deployment, confidentialPort, securityContext, exchange, tokenStore);
+
+ return keycloakAuthenticate(exchange, securityContext, authenticator);
+ }
+
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
new file mode 100755
index 0000000..5dc71e5
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowCookieTokenStore.java
@@ -0,0 +1,93 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in cookie
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowCookieTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(UndertowCookieTokenStore.class);
+
+ private final HttpFacade facade;
+ private final KeycloakDeployment deployment;
+ private final SecurityContext securityContext;
+
+ public UndertowCookieTokenStore(HttpFacade facade, KeycloakDeployment deployment,
+ SecurityContext securityContext) {
+ this.facade = facade;
+ this.deployment = deployment;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) {
+ log.debug("Account was not in cookie or was invalid, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = new KeycloakUndertowAccount(principal);
+
+ if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+ log.debug("Account in session belongs to a different realm than for this request.");
+ return false;
+ }
+
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ return true;
+ } else {
+ log.debug("Account was not active, removing cookie and returning false");
+ CookieTokenStore.removeCookie(facade);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(deployment, facade, secContext);
+ }
+
+ @Override
+ public void logout() {
+ KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
+ if (principal == null) return;
+
+ CookieTokenStore.removeCookie(facade);
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
+ }
+
+ @Override
+ public void saveRequest() {
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return false;
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationManagementWrapper.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationManagementWrapper.java
new file mode 100644
index 0000000..d6ca115
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowNodesRegistrationManagementWrapper.java
@@ -0,0 +1,27 @@
+package org.keycloak.adapters.undertow;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.keycloak.adapters.NodesRegistrationManagement;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowNodesRegistrationManagementWrapper implements ServletContextListener {
+
+ private final NodesRegistrationManagement delegate;
+
+ public UndertowNodesRegistrationManagementWrapper(NodesRegistrationManagement delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ delegate.stop();
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
new file mode 100755
index 0000000..8ad97ef
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowPreAuthActionsHandler.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpHandler;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.PreAuthActionsHandler;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowPreAuthActionsHandler implements HttpHandler {
+
+ private static final Logger log = Logger.getLogger(UndertowPreAuthActionsHandler.class);
+ protected HttpHandler next;
+ protected SessionManager sessionManager;
+ protected UndertowUserSessionManagement userSessionManagement;
+ protected AdapterDeploymentContext deploymentContext;
+
+ public UndertowPreAuthActionsHandler(AdapterDeploymentContext deploymentContext,
+ UndertowUserSessionManagement userSessionManagement,
+ SessionManager sessionManager,
+ HttpHandler next) {
+ this.next = next;
+ this.deploymentContext = deploymentContext;
+ this.sessionManager = sessionManager;
+ this.userSessionManagement = userSessionManagement;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) throws Exception {
+ UndertowHttpFacade facade = createFacade(exchange);
+ SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
+ PreAuthActionsHandler handler = new PreAuthActionsHandler(bridge, deploymentContext, facade);
+ if (handler.handleRequest()) return;
+ next.handleRequest(exchange);
+ }
+
+ public UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+ return new OIDCUndertowHttpFacade(exchange);
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
new file mode 100755
index 0000000..61f8088
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowRequestAuthenticator.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowRequestAuthenticator extends AbstractUndertowRequestAuthenticator {
+ public UndertowRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
+ SecurityContext securityContext, HttpServerExchange exchange, AdapterTokenStore tokenStore) {
+ super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
+ }
+
+ @Override
+ protected KeycloakUndertowAccount createAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
+ return new KeycloakUndertowAccount(principal);
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
new file mode 100755
index 0000000..f9fa6c0
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/java/org/keycloak/adapters/undertow/UndertowSessionTokenStore.java
@@ -0,0 +1,103 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.util.Sessions;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+
+/**
+ * Per-request object. Storage of tokens in undertow session.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UndertowSessionTokenStore implements AdapterTokenStore {
+
+ protected static Logger log = Logger.getLogger(UndertowSessionTokenStore.class);
+
+ private final HttpServerExchange exchange;
+ private final KeycloakDeployment deployment;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+
+ public UndertowSessionTokenStore(HttpServerExchange exchange, KeycloakDeployment deployment, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext) {
+ this.exchange = exchange;
+ this.deployment = deployment;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ // no-op on undertow
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ Session session = Sessions.getSession(exchange);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) {
+ log.debug("Account was not in session, returning null");
+ return false;
+ }
+
+ if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+ log.debug("Account in session belongs to a different realm than for this request.");
+ return false;
+ }
+
+ account.setCurrentRequestInfo(deployment, this);
+ if (account.checkActive()) {
+ log.debug("Cached account found");
+ securityContext.authenticationComplete(account, "KEYCLOAK", false);
+ ((AbstractUndertowRequestAuthenticator)authenticator).propagateKeycloakContext(account);
+ return true;
+ } else {
+ log.debug("Account was not active, returning false");
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ session.invalidate(exchange);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveRequest() {
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return false;
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ Session session = Sessions.getOrCreateSession(exchange);
+ session.setAttribute(KeycloakUndertowAccount.class.getName(), account);
+ sessionManagement.login(session.getSessionManager());
+ }
+
+ @Override
+ public void logout() {
+ Session session = Sessions.getSession(exchange);
+ if (session == null) return;
+ KeycloakUndertowAccount account = (KeycloakUndertowAccount)session.getAttribute(KeycloakUndertowAccount.class.getName());
+ if (account == null) return;
+ session.removeAttribute(KeycloakUndertowAccount.class.getName());
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ // no-op
+ }
+}
diff --git a/adapters/oidc/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension b/adapters/oidc/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
new file mode 100755
index 0000000..fc0939d
--- /dev/null
+++ b/adapters/oidc/undertow/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
@@ -0,0 +1 @@
+org.keycloak.adapters.undertow.KeycloakServletExtension
adapters/oidc/wildfly/pom.xml 21(+21 -0)
diff --git a/adapters/oidc/wildfly/pom.xml b/adapters/oidc/wildfly/pom.xml
new file mode 100644
index 0000000..4650faa
--- /dev/null
+++ b/adapters/oidc/wildfly/pom.xml
@@ -0,0 +1,21 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak WildFly Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-wildfly-integration-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>wildfly-adapter</module>
+ <module>wf8-subsystem</module>
+ <module>wildfly-subsystem</module>
+ </modules>
+</project>
\ No newline at end of file
adapters/oidc/wildfly/wf8-subsystem/pom.xml 115(+115 -0)
diff --git a/adapters/oidc/wildfly/wf8-subsystem/pom.xml b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
new file mode 100755
index 0000000..c1f205c
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wf8-subsystem</artifactId>
+ <name>Keycloak Wildfly 8 Adapter Subsystem</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <properties>
+ <wildfly.version>8.2.0.Final</wildfly.version>
+ <wildfly.core.version>8.2.0.Final</wildfly.core.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>false</redirectTestOutputToFile>
+ <enableAssertions>true</enableAssertions>
+ <systemProperties>
+ <property>
+ <name>jboss.home</name>
+ <value>${jboss.home}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-controller</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-server</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-web-common</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-annotations</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-processor</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-subsystem-test-framework</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java
new file mode 100755
index 0000000..56b8ef1
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+/**
+ * Add a credential to a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialAddHandler extends AbstractAddStepHandler {
+
+ public CredentialAddHandler(AttributeDefinition... attributes) {
+ super(attributes);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addCredential(operation, context.resolveExpressions(model));
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java
new file mode 100755
index 0000000..6083e93
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+/**
+ * Defines attributes and operations for a credential.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class CredentialDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "credential";
+
+ protected static final AttributeDefinition VALUE =
+ new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
+ .build();
+
+ public CredentialDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ new CredentialAddHandler(VALUE),
+ CredentialRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler());
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java
new file mode 100644
index 0000000..510f3ed
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update a credential value.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateCredential(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateCredential(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java
new file mode 100644
index 0000000..f3f7818
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a credential from a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public final class CredentialRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler();
+
+ private CredentialRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeCredential(operation);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java
new file mode 100755
index 0000000..4399291
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.common.WarMetaData;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.keycloak.subsystem.wf8.logging.KeycloakLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
+ protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class);
+
+ // This param name is defined again in Keycloak Undertow Integration class
+ // org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in
+ // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
+ public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
+
+ // not sure if we need this yet, keeping here just in case
+ protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
+ String deploymentName = deploymentUnit.getName();
+ if (!service.isSecureDeployment(deploymentName)) {
+ return;
+ }
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) return;
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) return;
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null || !loginConfig.getAuthMethod().equalsIgnoreCase("KEYCLOAK")) {
+ return;
+ }
+
+ webMetaData.setSecurityDomain("keycloak");
+ }
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ String deploymentName = deploymentUnit.getName();
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+ if (service.isSecureDeployment(deploymentName)) {
+ addKeycloakAuthData(phaseContext, deploymentName, service);
+ }
+
+ // FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
+
+ // todo notsure if we need this
+ // addSecurityDomain(deploymentUnit, service);
+ }
+
+ private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
+ }
+
+ addJSONData(service.getJSON(deploymentName), warMetaData);
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null) {
+ loginConfig = new LoginConfigMetaData();
+ webMetaData.setLoginConfig(loginConfig);
+ }
+ loginConfig.setAuthMethod("KEYCLOAK");
+ loginConfig.setRealmName(service.getRealmName(deploymentName));
+ KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
+ }
+
+ private void addJSONData(String json, WarMetaData warMetaData) {
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<ParamValueMetaData>();
+ }
+
+ ParamValueMetaData param = new ParamValueMetaData();
+ param.setParamName(AUTH_DATA_PARAM_NAME);
+ param.setParamValue(json);
+ contextParams.add(param);
+
+ webMetaData.setContextParams(contextParams);
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
new file mode 100755
index 0000000..2a0b93c
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
+
+/**
+ * This service keeps track of the entire Keycloak management model so as to provide
+ * adapter configuration to each deployment at deploy time.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class KeycloakAdapterConfigService {
+
+ private static final String CREDENTIALS_JSON_NAME = "credentials";
+
+ private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
+
+ public static KeycloakAdapterConfigService getInstance() {
+ return INSTANCE;
+ }
+
+ private final Map<String, ModelNode> realms = new HashMap<String, ModelNode>();
+
+ // keycloak-secured deployments
+ private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
+
+
+ private KeycloakAdapterConfigService() {
+ }
+
+ public void addRealm(ModelNode operation, ModelNode model) {
+ this.realms.put(realmNameFromOp(operation), model.clone());
+ }
+
+ public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode realm = this.realms.get(realmNameFromOp(operation));
+ realm.get(attrName).set(resolvedValue);
+ }
+
+ public void removeRealm(ModelNode operation) {
+ this.realms.remove(realmNameFromOp(operation));
+ }
+
+ public void addSecureDeployment(ModelNode operation, ModelNode model) {
+ ModelNode deployment = model.clone();
+ this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
+ }
+
+ public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(attrName).set(resolvedValue);
+ }
+
+ public void removeSecureDeployment(ModelNode operation) {
+ this.secureDeployments.remove(deploymentNameFromOp(operation));
+ }
+
+ public void addCredential(ModelNode operation, ModelNode model) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ credentials = new ModelNode();
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ if (!credentialName.contains(".")) {
+ credentials.get(credentialName).set(model.get("value").asString());
+ } else {
+ String[] parts = credentialName.split("\\.");
+ String provider = parts[0];
+ String property = parts[1];
+ ModelNode credential = credentials.get(provider);
+ if (!credential.isDefined()) {
+ credential = new ModelNode();
+ }
+ credential.get(property).set(model.get("value").asString());
+ credentials.set(provider, credential);
+ }
+
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
+ }
+
+ public void removeCredential(ModelNode operation) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.remove(credentialName);
+ }
+
+ public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.get(credentialName).set(resolvedValue);
+ }
+
+ private ModelNode credentialsFromOp(ModelNode operation) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ return deployment.get(CREDENTIALS_JSON_NAME);
+ }
+
+ private String realmNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
+ }
+
+ private String deploymentNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
+ }
+
+ private String credentialNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
+ }
+
+ private String valueFromOpAddress(String addrElement, ModelNode operation) {
+ String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
+ if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());
+ return deploymentName;
+ }
+
+ private String getValueOfAddrElement(ModelNode address, String elementName) {
+ for (ModelNode element : address.asList()) {
+ if (element.has(elementName)) return element.get(elementName).asString();
+ }
+
+ return null;
+ }
+
+ public String getRealmName(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ return deployment.get(RealmDefinition.TAG_NAME).asString();
+
+ }
+
+ public String getJSON(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
+ ModelNode realm = this.realms.get(realmName);
+
+ ModelNode json = new ModelNode();
+ json.get(RealmDefinition.TAG_NAME).set(realmName);
+
+ // Realm values set first. Some can be overridden by deployment values.
+ if (realm != null) setJSONValues(json, realm);
+ setJSONValues(json, deployment);
+ return json.toJSONString(true);
+ }
+
+ private void setJSONValues(ModelNode json, ModelNode values) {
+ for (Property prop : values.asPropertyList()) {
+ String name = prop.getName();
+ ModelNode value = prop.getValue();
+ if (value.isDefined()) {
+ json.get(name).set(value);
+ }
+ }
+ }
+
+ public boolean isSecureDeployment(String deploymentName) {
+ //log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
+
+ return this.secureDeployments.containsKey(deploymentName);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
new file mode 100755
index 0000000..efb9681
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
+ private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ // Next phase, need to detect if this is a Keycloak deployment. If not, don't add the modules.
+
+ final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+ final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+ addCommonModules(moduleSpecification, moduleLoader);
+ addPlatformSpecificModules(moduleSpecification, moduleLoader);
+ }
+
+ private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
+ }
+
+ abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java
new file mode 100755
index 0000000..7008fb6
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * Add platform-specific modules for WildFly.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
+ private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
+
+ @Override
+ protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java
new file mode 100755
index 0000000..6049f10
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.wf8.extension;
+
+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;
+import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
+import org.jboss.as.controller.parsing.ExtensionParsingContext;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.keycloak.subsystem.wf8.logging.KeycloakLogger;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
+
+
+/**
+ * Main Extension class for the subsystem.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakExtension implements Extension {
+
+ public static final String SUBSYSTEM_NAME = "keycloak";
+ public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.1";
+ private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
+ static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+ private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions";
+ private static final int MANAGEMENT_API_MAJOR_VERSION = 1;
+ private static final int MANAGEMENT_API_MINOR_VERSION = 0;
+ private static final int MANAGEMENT_API_MICRO_VERSION = 0;
+ static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+ private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
+ static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
+ static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
+ static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
+
+ public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
+ StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
+ for (String kp : keyPrefix) {
+ prefix.append('.').append(kp);
+ }
+ return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeParsers(final ExtensionParsingContext context) {
+ context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(final ExtensionContext context) {
+ KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension");
+ final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MANAGEMENT_API_MAJOR_VERSION, MANAGEMENT_API_MINOR_VERSION, MANAGEMENT_API_MICRO_VERSION);
+
+ ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
+ registration.registerSubModel(REALM_DEFINITION);
+ ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
+ secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
+
+ subsystem.registerXMLElementWriter(PARSER);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java
new file mode 100755
index 0000000..3be483e
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java
@@ -0,0 +1,63 @@
+/*
+ * 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.wf8.extension;
+
+
+import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.as.server.AbstractDeploymentChainStep;
+import org.jboss.as.server.DeploymentProcessorTarget;
+import org.jboss.as.server.deployment.Phase;
+import org.jboss.dmr.ModelNode;
+
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+/**
+ * The Keycloak subsystem add update handler.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
+
+ static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
+
+ @Override
+ protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) {
+ context.addStep(new AbstractDeploymentChainStep() {
+ @Override
+ protected void execute(DeploymentProcessorTarget processorTarget) {
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, chooseDependencyProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME,
+ Phase.POST_MODULE, // PHASE
+ Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
+ chooseConfigDeploymentProcessor());
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorWildFly();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java
new file mode 100644
index 0000000..a6093cf
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+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;
+
+/**
+ * Definition of subsystem=keycloak.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
+ protected KeycloakSubsystemDefinition() {
+ super(KeycloakExtension.SUBSYSTEM_PATH,
+ KeycloakExtension.getResourceDescriptionResolver("subsystem"),
+ KeycloakSubsystemAdd.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE
+ );
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java
new file mode 100755
index 0000000..fef809e
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a new realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmAddHandler extends AbstractAddStepHandler {
+
+ public static RealmAddHandler INSTANCE = new RealmAddHandler();
+
+ private RealmAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add realm. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) {
+ attrib.validateAndSet(operation, model);
+ }
+
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
+ //TODO: externalize message
+ throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addRealm(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java
new file mode 100755
index 0000000..628a5d7
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for the Realm
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "realm";
+
+
+ protected static final List<SimpleAttributeDefinition> REALM_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0]));
+
+ public RealmDefinition() {
+ super(PathElement.pathElement("realm"),
+ KeycloakExtension.getResourceDescriptionResolver("realm"),
+ RealmAddHandler.INSTANCE,
+ RealmRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ //TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler);
+ }
+ }
+
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java
new file mode 100644
index 0000000..84d8ab0
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler();
+
+ private RealmRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeRealm(operation);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java
new file mode 100755
index 0000000..b1062c6
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update an attribute on a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public RealmWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateRealm(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateRealm(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java
new file mode 100755
index 0000000..66cb8a7
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a deployment to a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentAddHandler extends AbstractAddStepHandler {
+
+ public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
+
+ private SecureDeploymentAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
+ attr.validateAndSet(operation, model);
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addSecureDeployment(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
new file mode 100755
index 0000000..d16a25e
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
@@ -0,0 +1,130 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "secure-deployment";
+
+ protected static final SimpleAttributeDefinition REALM =
+ new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
+ .setXmlName("realm")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition RESOURCE =
+ new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
+ .setXmlName("resource")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
+ new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
+ .setXmlName("use-resource-role-mappings")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition BEARER_ONLY =
+ new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
+ .setXmlName("bearer-only")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
+ new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
+ .setXmlName("enable-basic-auth")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
+ new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
+ .setXmlName("public-client")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+
+ protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES);
+
+ public SecureDeploymentDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ SecureDeploymentAddHandler.INSTANCE,
+ SecureDeploymentRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
+ }
+ }
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java
new file mode 100644
index 0000000..6629d08
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a secure-deployment from a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler();
+
+ private SecureDeploymentRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeSecureDeployment(operation);
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java
new file mode 100755
index 0000000..3788c1b
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.dmr.ModelNode;
+
+import java.util.List;
+
+/**
+ * Update an attribute on a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public SecureDeploymentWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
+ this(definitions.toArray(new AttributeDefinition[definitions.size()]));
+ }
+
+ public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ hh.setHandback(ckService);
+ ckService.updateSecureDeployment(operation, attributeName, resolvedValue);
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateSecureDeployment(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
new file mode 100755
index 0000000..35c4b3a
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
@@ -0,0 +1,228 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.operations.validation.IntRangeValidator;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines attributes that can be present in both a realm and an application (secure-deployment).
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SharedAttributeDefinitons {
+
+ protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY =
+ new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, true)
+ .setXmlName("realm-public-key")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL =
+ new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, true)
+ .setXmlName("auth-server-url")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition SSL_REQUIRED =
+ new SimpleAttributeDefinitionBuilder("ssl-required", ModelType.STRING, true)
+ .setXmlName("ssl-required")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode("external"))
+ .build();
+ protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME =
+ new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true)
+ .setXmlName("allow-any-hostname")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER =
+ new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true)
+ .setXmlName("disable-trust-manager")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE =
+ new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true)
+ .setXmlName("truststore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true)
+ .setXmlName("truststore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE =
+ new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true)
+ .setXmlName("connection-pool-size")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(0, true))
+ .build();
+
+ protected static final SimpleAttributeDefinition ENABLE_CORS =
+ new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true)
+ .setXmlName("enable-cors")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE =
+ new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true)
+ .setXmlName("client-keystore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true)
+ .setXmlName("client-keystore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true)
+ .setXmlName("client-key-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_MAX_AGE =
+ new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true)
+ .setXmlName("cors-max-age")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true)
+ .setXmlName("cors-allowed-headers")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true)
+ .setXmlName("cors-allowed-methods")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
+ new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
+ .setXmlName("expose-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
+ new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
+ .setXmlName("auth-server-url-for-backend-requests")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
+ new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
+ .setXmlName("always-refresh-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
+ new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
+ .setXmlName("register-node-at-startup")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
+ new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
+ .setXmlName("register-node-period")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition TOKEN_STORE =
+ new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
+ .setXmlName("token-store")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
+ new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
+ .setXmlName("principal-attribute")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+
+
+
+ protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ATTRIBUTES.add(REALM_PUBLIC_KEY);
+ ATTRIBUTES.add(AUTH_SERVER_URL);
+ ATTRIBUTES.add(TRUSTSTORE);
+ ATTRIBUTES.add(TRUSTSTORE_PASSWORD);
+ ATTRIBUTES.add(SSL_REQUIRED);
+ ATTRIBUTES.add(ALLOW_ANY_HOSTNAME);
+ ATTRIBUTES.add(DISABLE_TRUST_MANAGER);
+ ATTRIBUTES.add(CONNECTION_POOL_SIZE);
+ ATTRIBUTES.add(ENABLE_CORS);
+ ATTRIBUTES.add(CLIENT_KEYSTORE);
+ ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD);
+ ATTRIBUTES.add(CLIENT_KEY_PASSWORD);
+ ATTRIBUTES.add(CORS_MAX_AGE);
+ ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
+ ATTRIBUTES.add(CORS_ALLOWED_METHODS);
+ ATTRIBUTES.add(EXPOSE_TOKEN);
+ ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
+ ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
+ ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
+ ATTRIBUTES.add(REGISTER_NODE_PERIOD);
+ ATTRIBUTES.add(TOKEN_STORE);
+ ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
+ }
+
+ /**
+ * truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.
+ *
+ * @param attributes The full set of attributes.
+ *
+ * @return <code>true</code> if the attributes are valid, <code>false</code> otherwise.
+ */
+ public static boolean validateTruststoreSetIfRequired(ModelNode attributes) {
+ if (isSet(attributes, DISABLE_TRUST_MANAGER)) {
+ return true;
+ }
+
+ if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) {
+ return true;
+ }
+
+ return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD);
+ }
+
+ private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
+ ModelNode attribute = attributes.get(def.getName());
+
+ if (def.getType() == ModelType.BOOLEAN) {
+ return attribute.isDefined() && attribute.asBoolean();
+ }
+
+ return attribute.isDefined() && !attribute.asString().isEmpty();
+ }
+
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java
new file mode 100755
index 0000000..292fa65
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/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.wf8.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/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java
new file mode 100755
index 0000000..9d456c8
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/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.wf8.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/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100644
index 0000000..1f25766
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.wf8.extension.KeycloakExtension
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
new file mode 100755
index 0000000..c00bd8d
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
@@ -0,0 +1,72 @@
+keycloak.subsystem=Keycloak adapter subsystem
+keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
+keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
+keycloak.subsystem.realm=A Keycloak realm.
+keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
+
+keycloak.realm=A Keycloak realm.
+keycloak.realm.add=Add a realm definition to the subsystem.
+keycloak.realm.remove=Remove a realm from the subsystem.
+keycloak.realm.realm-public-key=Public key of the realm
+keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
+keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.realm.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.realm.allow-any-hostname=SSL Setting
+keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
+keycloak.realm.truststore-password=Password of the Truststore
+keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.realm.enable-cors=Enable Keycloak CORS support
+keycloak.realm.client-keystore=n/a
+keycloak.realm.client-keystore-password=n/a
+keycloak.realm.client-key-password=n/a
+keycloak.realm.cors-max-age=CORS max-age header
+keycloak.realm.cors-allowed-headers=CORS allowed headers
+keycloak.realm.cors-allowed-methods=CORS allowed methods
+keycloak.realm.expose-token=Enable secure URL that exposes access token
+keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.realm.always-refresh-token=Refresh token on every single web request
+keycloak.realm.register-node-at-startup=Cluster setting
+keycloak.realm.register-node-period=how often to re-register node
+keycloak.realm.token-store=cookie or session storage for auth session data
+keycloak.realm.principal-attribute=token attribute to use to set Principal name
+
+
+keycloak.secure-deployment=A deployment secured by Keycloak
+keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm=Keycloak realm
+keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm-public-key=Public key of the realm
+keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
+keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.secure-deployment.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.secure-deployment.allow-any-hostname=SSL Setting
+keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
+keycloak.secure-deployment.truststore-password=Password of the Truststore
+keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.secure-deployment.resource=Application name
+keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
+keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.bearer-only=Bearer Token Auth only
+keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
+keycloak.secure-deployment.public-client=Public client
+keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
+keycloak.secure-deployment.client-keystore=n/a
+keycloak.secure-deployment.client-keystore-password=n/a
+keycloak.secure-deployment.client-key-password=n/a
+keycloak.secure-deployment.cors-max-age=CORS max-age header
+keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
+keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
+keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
+keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
+keycloak.secure-deployment.register-node-at-startup=Cluster setting
+keycloak.secure-deployment.register-node-period=how often to re-register node
+keycloak.secure-deployment.token-store=cookie or session storage for auth session data
+keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
+
+keycloak.secure-deployment.credential=Credential value
+
+keycloak.credential=Credential
+keycloak.credential.value=Credential value
+keycloak.credential.add=Credential add
+keycloak.credential.remove=Credential remove
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
new file mode 100755
index 0000000..75de38a
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="urn:jboss:domain:keycloak:1.1"
+ xmlns="urn:jboss:domain:keycloak: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 adapter subsystem, used to register deployments managed by Keycloak
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
+ <xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="realm-type">
+ <xs:all>
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </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="secure-deployment-type">
+ <xs:all>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="public-client" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ </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="credential-type" mixed="true">
+ <xs:sequence maxOccurs="unbounded" minOccurs="0">
+ <xs:any processContents="lax"></xs:any>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
+ </xs:complexType>
+</xs:schema>
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java b/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java
new file mode 100755
index 0000000..1afeec4
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinitionTestCase {
+
+ private ModelNode model;
+
+ @Before
+ public void setUp() {
+ model = new ModelNode();
+ model.get("realm").set("demo");
+ model.get("resource").set("customer-portal");
+ model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ model.get("expose-token").set(true);
+ ModelNode credential = new ModelNode();
+ credential.get("password").set("password");
+ model.get("credentials").set(credential);
+ }
+
+ @Test
+ public void testIsTruststoreSetIfRequired() throws Exception {
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java b/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
new file mode 100755
index 0000000..6089293
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
@@ -0,0 +1,99 @@
+/*
+ * 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.wf8.extension;
+
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
+import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
+import org.jboss.dmr.ModelNode;
+import org.junit.Test;
+
+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(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension());
+ }
+
+ @Test
+ public void testJson() throws Exception {
+ ModelNode node = new ModelNode();
+ node.get("realm").set("demo");
+ node.get("resource").set("customer-portal");
+ node.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ node.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ node.get("ssl-required").set("external");
+ node.get("expose-token").set(true);
+
+ ModelNode jwtCredential = new ModelNode();
+ jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+ jwtCredential.get("client-keystore-password").set("changeit");
+ ModelNode credential = new ModelNode();
+ credential.get("jwt").set(jwtCredential);
+ node.get("credentials").set(credential);
+
+ System.out.println("json=" + node.toJSONString(false));
+ }
+
+ @Test
+ public void testJsonFromSignedJWTCredentials() {
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+ ModelNode deploymentOp = new ModelNode();
+ deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ ModelNode deployment = new ModelNode();
+ deployment.get("realm").set("demo");
+ deployment.get("resource").set("customer-portal");
+ service.addSecureDeployment(deploymentOp, deployment);
+
+ addCredential(addr, service, "secret", "secret1");
+ addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+ addCredential(addr, service, "jwt.token-timeout", "10");
+
+ System.out.println("Deployment: " + service.getJSON("foo"));
+ }
+
+ private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+ PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+ ModelNode credOp = new ModelNode();
+ credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+ ModelNode credential = new ModelNode();
+ credential.get("value").set(value);
+ service.addCredential(credOp, credential);
+ }
+
+
+ @Override
+ protected String getSubsystemXml() throws IOException {
+ return readResource("keycloak-1.1.xml");
+ }
+}
diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
new file mode 100644
index 0000000..e512f0e
--- /dev/null
+++ b/adapters/oidc/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
@@ -0,0 +1,26 @@
+<subsystem xmlns="urn:jboss:domain:keycloak:1.1">
+ <secure-deployment name="web-console">
+ <realm>master</realm>
+ <resource>web-console</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
+ </secure-deployment>
+ <secure-deployment name="http-endpoint">
+ <realm>master</realm>
+ <resource>http-endpoint</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="jwt">
+ <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+ </credential>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
adapters/oidc/wildfly/wildfly-adapter/pom.xml 108(+108 -0)
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
new file mode 100755
index 0000000..d1ef366
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-wildfly-adapter</artifactId>
+ <name>Keycloak Wildfly Integration</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jboss-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.picketbox</groupId>
+ <artifactId>picketbox</artifactId>
+ <version>4.0.20.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/SecurityInfoHelper.java b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/SecurityInfoHelper.java
new file mode 100755
index 0000000..33c149b
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/SecurityInfoHelper.java
@@ -0,0 +1,116 @@
+package org.keycloak.adapters.wildfly;
+
+import org.jboss.security.NestableGroup;
+import org.jboss.security.SecurityConstants;
+import org.jboss.security.SecurityContextAssociation;
+import org.jboss.security.SimpleGroup;
+import org.jboss.security.SimplePrincipal;
+import org.keycloak.adapters.spi.KeycloakAccount;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SecurityInfoHelper {
+ public static void propagateSessionInfo(KeycloakAccount account) {
+ Subject subject = new Subject();
+ Set<Principal> principals = subject.getPrincipals();
+ principals.add(account.getPrincipal());
+ Group[] roleSets = getRoleSets(account.getRoles());
+ for (int g = 0; g < roleSets.length; g++) {
+ Group group = roleSets[g];
+ String name = group.getName();
+ Group subjectGroup = createGroup(name, principals);
+ if (subjectGroup instanceof NestableGroup) {
+ /* A NestableGroup only allows Groups to be added to it so we
+ need to add a SimpleGroup to subjectRoles to contain the roles
+ */
+ SimpleGroup tmp = new SimpleGroup("Roles");
+ subjectGroup.addMember(tmp);
+ subjectGroup = tmp;
+ }
+ // Copy the group members to the Subject group
+ Enumeration<? extends Principal> members = group.members();
+ while (members.hasMoreElements()) {
+ Principal role = (Principal) members.nextElement();
+ subjectGroup.addMember(role);
+ }
+ }
+ // add the CallerPrincipal group if none has been added in getRoleSets
+ Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
+ callerGroup.addMember(account.getPrincipal());
+ principals.add(callerGroup);
+ org.jboss.security.SecurityContext sc = SecurityContextAssociation.getSecurityContext();
+ Principal userPrincipal = getPrincipal(subject);
+ sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
+ }
+
+ /**
+ * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
+ * considered or the single subject inside the CallerPrincipal group.
+ *
+ * @param subject
+ * @return the authenticated subject
+ */
+ protected static Principal getPrincipal(Subject subject) {
+ Principal principal = null;
+ Principal callerPrincipal = null;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ if (principals != null && !principals.isEmpty()) {
+ for (Principal p : principals) {
+ if (!(p instanceof Group) && principal == null) {
+ principal = p;
+ }
+ if (p instanceof Group) {
+ Group g = Group.class.cast(p);
+ if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+ Enumeration<? extends Principal> e = g.members();
+ if (e.hasMoreElements())
+ callerPrincipal = e.nextElement();
+ }
+ }
+ }
+ }
+ }
+ return callerPrincipal == null ? principal : callerPrincipal;
+ }
+
+ protected static Group createGroup(String name, Set<Principal> principals) {
+ Group roles = null;
+ Iterator<Principal> iter = principals.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ if ((next instanceof Group) == false)
+ continue;
+ Group grp = (Group) next;
+ if (grp.getName().equals(name)) {
+ roles = grp;
+ break;
+ }
+ }
+ // If we did not find a group create one
+ if (roles == null) {
+ roles = new SimpleGroup(name);
+ principals.add(roles);
+ }
+ return roles;
+ }
+
+ protected static Group[] getRoleSets(Collection<String> roleSet) {
+ SimpleGroup roles = new SimpleGroup("Roles");
+ Group[] roleSets = {roles};
+ for (String role : roleSet) {
+ roles.addMember(new SimplePrincipal(role));
+ }
+ return roleSets;
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
new file mode 100755
index 0000000..53d8e0c
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyAuthenticationMechanism.java
@@ -0,0 +1,35 @@
+package org.keycloak.adapters.wildfly;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.api.ConfidentialPortManager;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
+import org.keycloak.adapters.undertow.ServletRequestAuthenticator;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflyAuthenticationMechanism extends ServletKeycloakAuthMech {
+
+ public WildflyAuthenticationMechanism(AdapterDeploymentContext deploymentContext,
+ UndertowUserSessionManagement userSessionManagement,
+ NodesRegistrationManagement nodesRegistrationManagement,
+ ConfidentialPortManager portManager, String errorPage) {
+ super(deploymentContext, userSessionManagement, nodesRegistrationManagement, portManager, errorPage);
+ }
+
+ @Override
+ protected ServletRequestAuthenticator createRequestAuthenticator(KeycloakDeployment deployment, HttpServerExchange exchange, SecurityContext securityContext, UndertowHttpFacade facade) {
+ int confidentialPort = getConfidentilPort(exchange);
+ AdapterTokenStore tokenStore = getTokenStore(exchange, facade, deployment, securityContext);
+ return new WildflyRequestAuthenticator(facade, deployment,
+ confidentialPort, securityContext, exchange, tokenStore);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java
new file mode 100755
index 0000000..4735deb
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyKeycloakServletExtension.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.wildfly;
+
+import io.undertow.servlet.api.DeploymentInfo;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.undertow.KeycloakServletExtension;
+import org.keycloak.adapters.undertow.ServletKeycloakAuthMech;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflyKeycloakServletExtension extends KeycloakServletExtension {
+ protected static Logger log = Logger.getLogger(WildflyKeycloakServletExtension.class);
+
+ @Override
+ protected ServletKeycloakAuthMech createAuthenticationMechanism(DeploymentInfo deploymentInfo, AdapterDeploymentContext deploymentContext,
+ UndertowUserSessionManagement userSessionManagement, NodesRegistrationManagement nodesRegistrationManagement) {
+ log.debug("creating WildflyAuthenticationMechanism");
+ return new WildflyAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, deploymentInfo.getConfidentialPortManager(), getErrorPage(deploymentInfo));
+
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
new file mode 100755
index 0000000..80ed882
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/wildfly/WildflyRequestAuthenticator.java
@@ -0,0 +1,136 @@
+package org.keycloak.adapters.wildfly;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.jboss.logging.Logger;
+import org.jboss.security.NestableGroup;
+import org.jboss.security.SecurityConstants;
+import org.jboss.security.SecurityContextAssociation;
+import org.jboss.security.SimpleGroup;
+import org.jboss.security.SimplePrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
+import org.keycloak.adapters.undertow.ServletRequestAuthenticator;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflyRequestAuthenticator extends ServletRequestAuthenticator {
+ protected static Logger log = Logger.getLogger(WildflyRequestAuthenticator.class);
+
+ public WildflyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort,
+ SecurityContext securityContext, HttpServerExchange exchange,
+ AdapterTokenStore tokenStore) {
+ super(facade, deployment, sslRedirectPort, securityContext, exchange, tokenStore);
+ }
+
+ @Override
+ protected void propagateKeycloakContext(KeycloakUndertowAccount account) {
+ super.propagateKeycloakContext(account);
+ SecurityInfoHelper.propagateSessionInfo(account);
+ log.debug("propagate security context to wildfly");
+ Subject subject = new Subject();
+ Set<Principal> principals = subject.getPrincipals();
+ principals.add(account.getPrincipal());
+ Group[] roleSets = getRoleSets(account.getRoles());
+ for (int g = 0; g < roleSets.length; g++) {
+ Group group = roleSets[g];
+ String name = group.getName();
+ Group subjectGroup = createGroup(name, principals);
+ if (subjectGroup instanceof NestableGroup) {
+ /* A NestableGroup only allows Groups to be added to it so we
+ need to add a SimpleGroup to subjectRoles to contain the roles
+ */
+ SimpleGroup tmp = new SimpleGroup("Roles");
+ subjectGroup.addMember(tmp);
+ subjectGroup = tmp;
+ }
+ // Copy the group members to the Subject group
+ Enumeration<? extends Principal> members = group.members();
+ while (members.hasMoreElements()) {
+ Principal role = (Principal) members.nextElement();
+ subjectGroup.addMember(role);
+ }
+ }
+ // add the CallerPrincipal group if none has been added in getRoleSets
+ Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
+ callerGroup.addMember(account.getPrincipal());
+ principals.add(callerGroup);
+ org.jboss.security.SecurityContext sc = SecurityContextAssociation.getSecurityContext();
+ Principal userPrincipal = getPrincipal(subject);
+ sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
+ }
+
+ /**
+ * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
+ * considered or the single subject inside the CallerPrincipal group.
+ *
+ * @param subject
+ * @return the authenticated subject
+ */
+ protected Principal getPrincipal(Subject subject) {
+ Principal principal = null;
+ Principal callerPrincipal = null;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ if (principals != null && !principals.isEmpty()) {
+ for (Principal p : principals) {
+ if (!(p instanceof Group) && principal == null) {
+ principal = p;
+ }
+ if (p instanceof Group) {
+ Group g = Group.class.cast(p);
+ if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+ Enumeration<? extends Principal> e = g.members();
+ if (e.hasMoreElements())
+ callerPrincipal = e.nextElement();
+ }
+ }
+ }
+ }
+ }
+ return callerPrincipal == null ? principal : callerPrincipal;
+ }
+
+ protected Group createGroup(String name, Set<Principal> principals) {
+ Group roles = null;
+ Iterator<Principal> iter = principals.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ if ((next instanceof Group) == false)
+ continue;
+ Group grp = (Group) next;
+ if (grp.getName().equals(name)) {
+ roles = grp;
+ break;
+ }
+ }
+ // If we did not find a group create one
+ if (roles == null) {
+ roles = new SimpleGroup(name);
+ principals.add(roles);
+ }
+ return roles;
+ }
+
+ protected Group[] getRoleSets(Collection<String> roleSet) {
+ SimpleGroup roles = new SimpleGroup("Roles");
+ Group[] roleSets = {roles};
+ for (String role : roleSet) {
+ roles.addMember(new SimplePrincipal(role));
+ }
+ return roleSets;
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension b/adapters/oidc/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
new file mode 100755
index 0000000..a46a78e
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
@@ -0,0 +1 @@
+org.keycloak.adapters.wildfly.WildflyKeycloakServletExtension
adapters/oidc/wildfly/wildfly-subsystem/pom.xml 105(+105 -0)
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/pom.xml b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
new file mode 100755
index 0000000..87b5028
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wildfly-subsystem</artifactId>
+ <name>Keycloak Wildfly Adapter Subsystem</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>false</redirectTestOutputToFile>
+ <enableAssertions>true</enableAssertions>
+ <systemProperties>
+ <property>
+ <name>jboss.home</name>
+ <value>${jboss.home}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-controller</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-web-common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-annotations</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-processor</artifactId>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-subsystem-test-framework</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java
new file mode 100755
index 0000000..2b9852c
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialAddHandler.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Add a credential to a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialAddHandler extends AbstractAddStepHandler {
+
+ public CredentialAddHandler(AttributeDefinition... attributes) {
+ super(attributes);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addCredential(operation, context.resolveExpressions(model));
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java
new file mode 100755
index 0000000..77dfd9d
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+/**
+ * Defines attributes and operations for a credential.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class CredentialDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "credential";
+
+ protected static final AttributeDefinition VALUE =
+ new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
+ .build();
+
+ public CredentialDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ new CredentialAddHandler(VALUE),
+ CredentialRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler());
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java
new file mode 100644
index 0000000..7aff17f
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialReadWriteAttributeHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update a credential value.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateCredential(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateCredential(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java
new file mode 100644
index 0000000..36923f0
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/CredentialRemoveHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a credential from a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public final class CredentialRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler();
+
+ private CredentialRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeCredential(operation);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java
new file mode 100755
index 0000000..f624627
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigDeploymentProcessor.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.common.WarMetaData;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.keycloak.subsystem.adapter.logging.KeycloakLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
+ protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class);
+
+ // This param name is defined again in Keycloak Undertow Integration class
+ // org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in
+ // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
+ public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
+
+ // not sure if we need this yet, keeping here just in case
+ protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
+ String deploymentName = deploymentUnit.getName();
+ if (!service.isSecureDeployment(deploymentName)) {
+ return;
+ }
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) return;
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) return;
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null || !loginConfig.getAuthMethod().equalsIgnoreCase("KEYCLOAK")) {
+ return;
+ }
+
+ webMetaData.setSecurityDomain("keycloak");
+ }
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ String deploymentName = deploymentUnit.getName();
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+ if (service.isSecureDeployment(deploymentName)) {
+ addKeycloakAuthData(phaseContext, deploymentName, service);
+ }
+
+ // FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
+
+ // todo notsure if we need this
+ // addSecurityDomain(deploymentUnit, service);
+ }
+
+ private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
+ }
+
+ addJSONData(service.getJSON(deploymentName), warMetaData);
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null) {
+ loginConfig = new LoginConfigMetaData();
+ webMetaData.setLoginConfig(loginConfig);
+ }
+ loginConfig.setAuthMethod("KEYCLOAK");
+ loginConfig.setRealmName(service.getRealmName(deploymentName));
+ KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
+ }
+
+ private void addJSONData(String json, WarMetaData warMetaData) {
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<ParamValueMetaData>();
+ }
+
+ ParamValueMetaData param = new ParamValueMetaData();
+ param.setParamName(AUTH_DATA_PARAM_NAME);
+ param.setParamValue(json);
+ contextParams.add(param);
+
+ webMetaData.setContextParams(contextParams);
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
new file mode 100755
index 0000000..8eb4b9e
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
+
+/**
+ * This service keeps track of the entire Keycloak management model so as to provide
+ * adapter configuration to each deployment at deploy time.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class KeycloakAdapterConfigService {
+
+ private static final String CREDENTIALS_JSON_NAME = "credentials";
+
+ private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
+
+ public static KeycloakAdapterConfigService getInstance() {
+ return INSTANCE;
+ }
+
+ private final Map<String, ModelNode> realms = new HashMap<String, ModelNode>();
+
+ // keycloak-secured deployments
+ private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
+
+
+ private KeycloakAdapterConfigService() {
+ }
+
+ public void addRealm(ModelNode operation, ModelNode model) {
+ this.realms.put(realmNameFromOp(operation), model.clone());
+ }
+
+ public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode realm = this.realms.get(realmNameFromOp(operation));
+ realm.get(attrName).set(resolvedValue);
+ }
+
+ public void removeRealm(ModelNode operation) {
+ this.realms.remove(realmNameFromOp(operation));
+ }
+
+ public void addSecureDeployment(ModelNode operation, ModelNode model) {
+ ModelNode deployment = model.clone();
+ this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
+ }
+
+ public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(attrName).set(resolvedValue);
+ }
+
+ public void removeSecureDeployment(ModelNode operation) {
+ this.secureDeployments.remove(deploymentNameFromOp(operation));
+ }
+
+ public void addCredential(ModelNode operation, ModelNode model) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ credentials = new ModelNode();
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ if (!credentialName.contains(".")) {
+ credentials.get(credentialName).set(model.get("value").asString());
+ } else {
+ String[] parts = credentialName.split("\\.");
+ String provider = parts[0];
+ String property = parts[1];
+ ModelNode credential = credentials.get(provider);
+ if (!credential.isDefined()) {
+ credential = new ModelNode();
+ }
+ credential.get(property).set(model.get("value").asString());
+ credentials.set(provider, credential);
+ }
+
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
+ }
+
+ public void removeCredential(ModelNode operation) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.remove(credentialName);
+ }
+
+ public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.get(credentialName).set(resolvedValue);
+ }
+
+ private ModelNode credentialsFromOp(ModelNode operation) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ return deployment.get(CREDENTIALS_JSON_NAME);
+ }
+
+ private String realmNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
+ }
+
+ private String deploymentNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
+ }
+
+ private String credentialNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
+ }
+
+ private String valueFromOpAddress(String addrElement, ModelNode operation) {
+ String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
+ if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());
+ return deploymentName;
+ }
+
+ private String getValueOfAddrElement(ModelNode address, String elementName) {
+ for (ModelNode element : address.asList()) {
+ if (element.has(elementName)) return element.get(elementName).asString();
+ }
+
+ return null;
+ }
+
+ public String getRealmName(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ return deployment.get(RealmDefinition.TAG_NAME).asString();
+
+ }
+
+ public String getJSON(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
+ ModelNode realm = this.realms.get(realmName);
+
+ ModelNode json = new ModelNode();
+ json.get(RealmDefinition.TAG_NAME).set(realmName);
+
+ // Realm values set first. Some can be overridden by deployment values.
+ if (realm != null) setJSONValues(json, realm);
+ setJSONValues(json, deployment);
+ return json.toJSONString(true);
+ }
+
+ private void setJSONValues(ModelNode json, ModelNode values) {
+ for (Property prop : values.asPropertyList()) {
+ String name = prop.getName();
+ ModelNode value = prop.getValue();
+ if (value.isDefined()) {
+ json.get(name).set(value);
+ }
+ }
+ }
+
+ public boolean isSecureDeployment(String deploymentName) {
+ //log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
+
+ return this.secureDeployments.containsKey(deploymentName);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java
new file mode 100755
index 0000000..cb246d4
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessor.java
@@ -0,0 +1,69 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
+ private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ // Next phase, need to detect if this is a Keycloak deployment. If not, don't add the modules.
+
+ final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+ final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+ addCommonModules(moduleSpecification, moduleLoader);
+ addPlatformSpecificModules(moduleSpecification, moduleLoader);
+ }
+
+ private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
+ }
+
+ abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java
new file mode 100755
index 0000000..a11bd01
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakDependencyProcessorWildFly.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * Add platform-specific modules for WildFly.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
+ private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
+
+ @Override
+ protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java
new file mode 100755
index 0000000..31f6957
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakExtension.java
@@ -0,0 +1,83 @@
+/*
+ * 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.extension;
+
+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;
+import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
+import org.jboss.as.controller.parsing.ExtensionParsingContext;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.keycloak.subsystem.adapter.logging.KeycloakLogger;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
+
+
+/**
+ * Main Extension class for the subsystem.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakExtension implements Extension {
+
+ public static final String SUBSYSTEM_NAME = "keycloak";
+ public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.1";
+ private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
+ static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+ private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions";
+ private static final 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();
+ static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
+ static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
+ static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
+
+ public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
+ StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
+ for (String kp : keyPrefix) {
+ prefix.append('.').append(kp);
+ }
+ return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeParsers(final ExtensionParsingContext context) {
+ context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(final ExtensionContext context) {
+ KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension");
+ final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION);
+
+ ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
+ registration.registerSubModel(REALM_DEFINITION);
+ ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
+ secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
+
+ subsystem.registerXMLElementWriter(PARSER);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java
new file mode 100755
index 0000000..81ad1ce
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemAdd.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.adapter.extension;
+
+
+import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.server.AbstractDeploymentChainStep;
+import org.jboss.as.server.DeploymentProcessorTarget;
+import org.jboss.as.server.deployment.Phase;
+import org.jboss.dmr.ModelNode;
+
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+
+/**
+ * The Keycloak subsystem add update handler.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
+
+ static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
+
+ @Override
+ protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model) {
+ context.addStep(new AbstractDeploymentChainStep() {
+ @Override
+ protected void execute(DeploymentProcessorTarget processorTarget) {
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, chooseDependencyProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME,
+ Phase.POST_MODULE, // PHASE
+ Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
+ chooseConfigDeploymentProcessor());
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorWildFly();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java
new file mode 100644
index 0000000..975fc6c
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemDefinition.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+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;
+
+/**
+ * Definition of subsystem=keycloak.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
+ protected KeycloakSubsystemDefinition() {
+ super(KeycloakExtension.SUBSYSTEM_PATH,
+ KeycloakExtension.getResourceDescriptionResolver("subsystem"),
+ KeycloakSubsystemAdd.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE
+ );
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
new file mode 100755
index 0000000..9a9e667
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
+import org.jboss.as.controller.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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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(KeycloakExtension.PATH_SUBSYSTEM));
+ list.add(addKeycloakSub);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ if (reader.getLocalName().equals(RealmDefinition.TAG_NAME)) {
+ readRealm(reader, list);
+ }
+ else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
+ readDeployment(reader, list);
+ }
+ }
+ }
+
+ // used for debugging
+ private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException {
+ return reader.nextTag();
+ }
+
+ private void readRealm(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
+ String realmName = readNameAttribute(reader);
+ ModelNode addRealm = new ModelNode();
+ addRealm.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(RealmDefinition.TAG_NAME, realmName));
+ addRealm.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ SimpleAttributeDefinition def = RealmDefinition.lookup(tagName);
+ if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName);
+ def.parseAndSetParameter(reader.getElementText(), addRealm, reader);
+ }
+
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
+ //TODO: externalize the message
+ throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+
+ list.add(addRealm);
+ }
+
+ private void readDeployment(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
+ String name = readNameAttribute(reader);
+ ModelNode addSecureDeployment = new ModelNode();
+ addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
+ addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (tagName.equals(CredentialDefinition.TAG_NAME)) {
+ readCredential(reader, addr, credentialsToAdd);
+ continue;
+ }
+
+ SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
+ if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
+ def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader);
+ }
+
+
+ /**
+ * TODO need to check realm-ref first.
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
+ //TODO: externalize the message
+ throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+ */
+
+ // Must add credentials after the deployment is added.
+ resourcesToAdd.add(addSecureDeployment);
+ resourcesToAdd.addAll(credentialsToAdd);
+ }
+
+ public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
+ String name = readNameAttribute(reader);
+
+ Map<String, String> values = new HashMap<>();
+ String textValue = null;
+ while (reader.hasNext()) {
+ int next = reader.next();
+ if (next == CHARACTERS) {
+ // text value of credential element (like for "secret" )
+ String text = reader.getText();
+ if (text == null || text.trim().isEmpty()) {
+ continue;
+ }
+ textValue = text;
+ } else if (next == START_ELEMENT) {
+ String key = reader.getLocalName();
+ reader.next();
+ String value = reader.getText();
+ reader.next();
+
+ values.put(key, value);
+ } else if (next == END_ELEMENT) {
+ break;
+ }
+ }
+
+ if (textValue != null) {
+ ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
+ credentialsToAdd.add(addCredential);
+ } else {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue());
+ credentialsToAdd.add(addCredential);
+ }
+ }
+ }
+
+ private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
+ ModelNode addCredential = new ModelNode();
+ addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
+ addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
+ return addCredential;
+ }
+
+ // expects that the current tag will have one single attribute called "name"
+ private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
+ String name = null;
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String attr = reader.getAttributeLocalName(i);
+ if (attr.equals("name")) {
+ name = reader.getAttributeValue(i);
+ continue;
+ }
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ if (name == null) {
+ throw ParseUtils.missingRequired(reader, Collections.singleton("name"));
+ }
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
+ context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
+ writeRealms(writer, context);
+ writeSecureDeployments(writer, context);
+ writer.writeEndElement();
+ }
+
+ private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
+ if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) {
+ return;
+ }
+ for (Property realm : context.getModelNode().get(RealmDefinition.TAG_NAME).asPropertyList()) {
+ writer.writeStartElement(RealmDefinition.TAG_NAME);
+ writer.writeAttribute("name", realm.getName());
+ ModelNode realmElements = realm.getValue();
+ for (AttributeDefinition element : RealmDefinition.ALL_ATTRIBUTES) {
+ element.marshallAsElement(realmElements, writer);
+ }
+
+ writer.writeEndElement();
+ }
+ }
+
+ private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
+ if (!context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).isDefined()) {
+ return;
+ }
+ for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).asPropertyList()) {
+ writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME);
+ writer.writeAttribute("name", deployment.getName());
+ ModelNode deploymentElements = deployment.getValue();
+ for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
+ element.marshallAsElement(deploymentElements, writer);
+ }
+
+ ModelNode credentials = deploymentElements.get(CredentialDefinition.TAG_NAME);
+ if (credentials.isDefined()) {
+ writeCredentials(writer, credentials);
+ }
+
+ writer.writeEndElement();
+ }
+ }
+
+ private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+ Map<String, Object> parsed = new LinkedHashMap<>();
+ for (Property credential : credentials.asPropertyList()) {
+ String credName = credential.getName();
+ String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+
+ if (credName.contains(".")) {
+ String[] parts = credName.split("\\.");
+ String provider = parts[0];
+ String propKey = parts[1];
+
+ Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
+ if (currentProviderMap == null) {
+ currentProviderMap = new LinkedHashMap<>();
+ parsed.put(provider, currentProviderMap);
+ }
+ currentProviderMap.put(propKey, credValue);
+ } else {
+ parsed.put(credName, credValue);
+ }
+ }
+
+ for (Map.Entry<String, Object> entry : parsed.entrySet()) {
+ writer.writeStartElement(CredentialDefinition.TAG_NAME);
+ writer.writeAttribute("name", entry.getKey());
+
+ Object value = entry.getValue();
+ if (value instanceof String) {
+ writeCharacters(writer, (String) value);
+ } else {
+ Map<String, String> credentialProps = (Map<String, String>) value;
+ for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
+ writer.writeStartElement(prop.getKey());
+ writeCharacters(writer, prop.getValue());
+ writer.writeEndElement();
+ }
+ }
+
+ writer.writeEndElement();
+ }
+ }
+
+ // code taken from org.jboss.as.controller.AttributeMarshaller
+ private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
+ if (content.indexOf('\n') > -1) {
+ // Multiline content. Use the overloaded variant that staxmapper will format
+ writer.writeCharacters(content);
+ } else {
+ // Staxmapper will just output the chars without adding newlines if this is used
+ char[] chars = content.toCharArray();
+ writer.writeCharacters(chars, 0, chars.length);
+ }
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java
new file mode 100755
index 0000000..1651ddf
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmAddHandler.java
@@ -0,0 +1,62 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a new realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmAddHandler extends AbstractAddStepHandler {
+
+ public static RealmAddHandler INSTANCE = new RealmAddHandler();
+
+ private RealmAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add realm. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) {
+ attrib.validateAndSet(operation, model);
+ }
+
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
+ //TODO: externalize message
+ throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addRealm(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java
new file mode 100755
index 0000000..160a6eb
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmDefinition.java
@@ -0,0 +1,87 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for the Realm
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "realm";
+
+
+ protected static final List<SimpleAttributeDefinition> REALM_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0]));
+
+ public RealmDefinition() {
+ super(PathElement.pathElement("realm"),
+ KeycloakExtension.getResourceDescriptionResolver("realm"),
+ RealmAddHandler.INSTANCE,
+ RealmRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ //TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler);
+ }
+ }
+
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java
new file mode 100644
index 0000000..16e4361
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler();
+
+ private RealmRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeRealm(operation);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java
new file mode 100755
index 0000000..f8f5e41
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/RealmWriteAttributeHandler.java
@@ -0,0 +1,54 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update an attribute on a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public RealmWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateRealm(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateRealm(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java
new file mode 100755
index 0000000..8bf263d
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentAddHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a deployment to a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentAddHandler extends AbstractAddStepHandler {
+
+ public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
+
+ private SecureDeploymentAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
+ attr.validateAndSet(operation, model);
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addSecureDeployment(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java
new file mode 100755
index 0000000..1932cc8
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentDefinition.java
@@ -0,0 +1,130 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "secure-deployment";
+
+ protected static final SimpleAttributeDefinition REALM =
+ new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
+ .setXmlName("realm")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition RESOURCE =
+ new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
+ .setXmlName("resource")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
+ new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
+ .setXmlName("use-resource-role-mappings")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition BEARER_ONLY =
+ new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
+ .setXmlName("bearer-only")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
+ new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
+ .setXmlName("enable-basic-auth")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
+ new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
+ .setXmlName("public-client")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+
+ protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES);
+
+ public SecureDeploymentDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ SecureDeploymentAddHandler.INSTANCE,
+ SecureDeploymentRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
+ }
+ }
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java
new file mode 100644
index 0000000..6e2aefc
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a secure-deployment from a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler();
+
+ private SecureDeploymentRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeSecureDeployment(operation);
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java
new file mode 100755
index 0000000..6ed9d55
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SecureDeploymentWriteAttributeHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.dmr.ModelNode;
+
+import java.util.List;
+
+/**
+ * Update an attribute on a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public SecureDeploymentWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
+ this(definitions.toArray(new AttributeDefinition[definitions.size()]));
+ }
+
+ public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ hh.setHandback(ckService);
+ ckService.updateSecureDeployment(operation, attributeName, resolvedValue);
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateSecureDeployment(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
new file mode 100755
index 0000000..f7abc04
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/SharedAttributeDefinitons.java
@@ -0,0 +1,228 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.operations.validation.IntRangeValidator;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines attributes that can be present in both a realm and an application (secure-deployment).
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SharedAttributeDefinitons {
+
+ protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY =
+ new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, true)
+ .setXmlName("realm-public-key")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL =
+ new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, true)
+ .setXmlName("auth-server-url")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition SSL_REQUIRED =
+ new SimpleAttributeDefinitionBuilder("ssl-required", ModelType.STRING, true)
+ .setXmlName("ssl-required")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode("external"))
+ .build();
+ protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME =
+ new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true)
+ .setXmlName("allow-any-hostname")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER =
+ new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true)
+ .setXmlName("disable-trust-manager")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE =
+ new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true)
+ .setXmlName("truststore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true)
+ .setXmlName("truststore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE =
+ new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true)
+ .setXmlName("connection-pool-size")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(0, true))
+ .build();
+
+ protected static final SimpleAttributeDefinition ENABLE_CORS =
+ new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true)
+ .setXmlName("enable-cors")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE =
+ new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true)
+ .setXmlName("client-keystore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true)
+ .setXmlName("client-keystore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true)
+ .setXmlName("client-key-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_MAX_AGE =
+ new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true)
+ .setXmlName("cors-max-age")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true)
+ .setXmlName("cors-allowed-headers")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true)
+ .setXmlName("cors-allowed-methods")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
+ new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
+ .setXmlName("expose-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
+ new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
+ .setXmlName("auth-server-url-for-backend-requests")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
+ new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
+ .setXmlName("always-refresh-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
+ new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
+ .setXmlName("register-node-at-startup")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
+ new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
+ .setXmlName("register-node-period")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition TOKEN_STORE =
+ new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
+ .setXmlName("token-store")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
+ new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
+ .setXmlName("principal-attribute")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+
+
+
+ protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ATTRIBUTES.add(REALM_PUBLIC_KEY);
+ ATTRIBUTES.add(AUTH_SERVER_URL);
+ ATTRIBUTES.add(TRUSTSTORE);
+ ATTRIBUTES.add(TRUSTSTORE_PASSWORD);
+ ATTRIBUTES.add(SSL_REQUIRED);
+ ATTRIBUTES.add(ALLOW_ANY_HOSTNAME);
+ ATTRIBUTES.add(DISABLE_TRUST_MANAGER);
+ ATTRIBUTES.add(CONNECTION_POOL_SIZE);
+ ATTRIBUTES.add(ENABLE_CORS);
+ ATTRIBUTES.add(CLIENT_KEYSTORE);
+ ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD);
+ ATTRIBUTES.add(CLIENT_KEY_PASSWORD);
+ ATTRIBUTES.add(CORS_MAX_AGE);
+ ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
+ ATTRIBUTES.add(CORS_ALLOWED_METHODS);
+ ATTRIBUTES.add(EXPOSE_TOKEN);
+ ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
+ ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
+ ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
+ ATTRIBUTES.add(REGISTER_NODE_PERIOD);
+ ATTRIBUTES.add(TOKEN_STORE);
+ ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
+ }
+
+ /**
+ * truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.
+ *
+ * @param attributes The full set of attributes.
+ *
+ * @return <code>true</code> if the attributes are valid, <code>false</code> otherwise.
+ */
+ public static boolean validateTruststoreSetIfRequired(ModelNode attributes) {
+ if (isSet(attributes, DISABLE_TRUST_MANAGER)) {
+ return true;
+ }
+
+ if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) {
+ return true;
+ }
+
+ return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD);
+ }
+
+ private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
+ ModelNode attribute = attributes.get(def.getName());
+
+ if (def.getType() == ModelType.BOOLEAN) {
+ return attribute.isDefined() && attribute.asBoolean();
+ }
+
+ return attribute.isDefined() && !attribute.asString().isEmpty();
+ }
+
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakLogger.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakLogger.java
new file mode 100755
index 0000000..de0bb4b
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/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.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/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakMessages.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/logging/KeycloakMessages.java
new file mode 100755
index 0000000..a45b0b8
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/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.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/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100644
index 0000000..b759b38
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.adapter.extension.KeycloakExtension
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
new file mode 100755
index 0000000..c00bd8d
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/extension/LocalDescriptions.properties
@@ -0,0 +1,72 @@
+keycloak.subsystem=Keycloak adapter subsystem
+keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
+keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
+keycloak.subsystem.realm=A Keycloak realm.
+keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
+
+keycloak.realm=A Keycloak realm.
+keycloak.realm.add=Add a realm definition to the subsystem.
+keycloak.realm.remove=Remove a realm from the subsystem.
+keycloak.realm.realm-public-key=Public key of the realm
+keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
+keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.realm.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.realm.allow-any-hostname=SSL Setting
+keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
+keycloak.realm.truststore-password=Password of the Truststore
+keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.realm.enable-cors=Enable Keycloak CORS support
+keycloak.realm.client-keystore=n/a
+keycloak.realm.client-keystore-password=n/a
+keycloak.realm.client-key-password=n/a
+keycloak.realm.cors-max-age=CORS max-age header
+keycloak.realm.cors-allowed-headers=CORS allowed headers
+keycloak.realm.cors-allowed-methods=CORS allowed methods
+keycloak.realm.expose-token=Enable secure URL that exposes access token
+keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.realm.always-refresh-token=Refresh token on every single web request
+keycloak.realm.register-node-at-startup=Cluster setting
+keycloak.realm.register-node-period=how often to re-register node
+keycloak.realm.token-store=cookie or session storage for auth session data
+keycloak.realm.principal-attribute=token attribute to use to set Principal name
+
+
+keycloak.secure-deployment=A deployment secured by Keycloak
+keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm=Keycloak realm
+keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm-public-key=Public key of the realm
+keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
+keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.secure-deployment.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.secure-deployment.allow-any-hostname=SSL Setting
+keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
+keycloak.secure-deployment.truststore-password=Password of the Truststore
+keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.secure-deployment.resource=Application name
+keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
+keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.bearer-only=Bearer Token Auth only
+keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
+keycloak.secure-deployment.public-client=Public client
+keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
+keycloak.secure-deployment.client-keystore=n/a
+keycloak.secure-deployment.client-keystore-password=n/a
+keycloak.secure-deployment.client-key-password=n/a
+keycloak.secure-deployment.cors-max-age=CORS max-age header
+keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
+keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
+keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
+keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
+keycloak.secure-deployment.register-node-at-startup=Cluster setting
+keycloak.secure-deployment.register-node-period=how often to re-register node
+keycloak.secure-deployment.token-store=cookie or session storage for auth session data
+keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
+
+keycloak.secure-deployment.credential=Credential value
+
+keycloak.credential=Credential
+keycloak.credential.value=Credential value
+keycloak.credential.add=Credential add
+keycloak.credential.remove=Credential remove
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
new file mode 100755
index 0000000..0abaac1
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="urn:jboss:domain:keycloak:1.1"
+ xmlns="urn:jboss:domain:keycloak: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 adapter subsystem, used to register deployments managed by Keycloak
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
+ <xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="realm-type">
+ <xs:all>
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </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="secure-deployment-type">
+ <xs:all>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="public-client" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ </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="credential-type" mixed="true">
+ <xs:sequence maxOccurs="unbounded" minOccurs="0">
+ <xs:any processContents="lax"></xs:any>
+ </xs:sequence>
+ <xs:attribute name="name" type="xs:string" use="required" />
+ </xs:complexType>
+</xs:schema>
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java
new file mode 100755
index 0000000..92a1958
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java
@@ -0,0 +1,86 @@
+/*
+ * 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.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinitionTestCase {
+
+ private ModelNode model;
+
+ @Before
+ public void setUp() {
+ model = new ModelNode();
+ model.get("realm").set("demo");
+ model.get("resource").set("customer-portal");
+ model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ model.get("expose-token").set(true);
+ ModelNode credential = new ModelNode();
+ credential.get("password").set("password");
+ model.get("credentials").set(credential);
+ }
+
+ @Test
+ public void testIsTruststoreSetIfRequired() throws Exception {
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+ }
+
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
new file mode 100755
index 0000000..4002382
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/SubsystemParsingTestCase.java
@@ -0,0 +1,110 @@
+/*
+ * 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.extension;
+
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
+import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
+import org.jboss.dmr.ModelNode;
+import org.junit.Test;
+
+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(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension());
+ }
+
+ @Test
+ public void testJson() throws Exception {
+ ModelNode node = new ModelNode();
+ node.get("realm").set("demo");
+ node.get("resource").set("customer-portal");
+ node.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ node.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ node.get("ssl-required").set("external");
+ node.get("expose-token").set(true);
+
+ ModelNode jwtCredential = new ModelNode();
+ jwtCredential.get("client-keystore-file").set("/tmp/keystore.jks");
+ jwtCredential.get("client-keystore-password").set("changeit");
+ ModelNode credential = new ModelNode();
+ credential.get("jwt").set(jwtCredential);
+ node.get("credentials").set(credential);
+
+ System.out.println("json=" + node.toJSONString(false));
+ }
+
+ @Test
+ public void testJsonFromSignedJWTCredentials() {
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement("subsystem", "keycloak"), PathElement.pathElement("secure-deployment", "foo"));
+ ModelNode deploymentOp = new ModelNode();
+ deploymentOp.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ ModelNode deployment = new ModelNode();
+ deployment.get("realm").set("demo");
+ deployment.get("resource").set("customer-portal");
+ service.addSecureDeployment(deploymentOp, deployment);
+
+ addCredential(addr, service, "secret", "secret1");
+ addCredential(addr, service, "jwt.client-keystore-file", "/tmp/foo.jks");
+ addCredential(addr, service, "jwt.token-timeout", "10");
+
+ System.out.println("Deployment: " + service.getJSON("foo"));
+ }
+
+ private void addCredential(PathAddress parent, KeycloakAdapterConfigService service, String key, String value) {
+ PathAddress credAddr = PathAddress.pathAddress(parent, PathElement.pathElement("credential", key));
+ ModelNode credOp = new ModelNode();
+ credOp.get(ModelDescriptionConstants.OP_ADDR).set(credAddr.toModelNode());
+ ModelNode credential = new ModelNode();
+ credential.get("value").set(value);
+ service.addCredential(credOp, credential);
+ }
+
+ @Override
+ protected String getSubsystemXml() throws IOException {
+ return readResource("keycloak-1.1.xml");
+ }
+
+ @Override
+ protected String getSubsystemXsdPath() throws Exception {
+ return "schema/wildfly-keycloak_1_1.xsd";
+ }
+
+ @Override
+ protected String[] getSubsystemTemplatePaths() throws IOException {
+ return new String[]{
+ "/subsystem-templates/keycloak-adapter.xml"
+ };
+ }
+}
diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
new file mode 100644
index 0000000..e512f0e
--- /dev/null
+++ b/adapters/oidc/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/extension/keycloak-1.1.xml
@@ -0,0 +1,26 @@
+<subsystem xmlns="urn:jboss:domain:keycloak:1.1">
+ <secure-deployment name="web-console">
+ <realm>master</realm>
+ <resource>web-console</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
+ </secure-deployment>
+ <secure-deployment name="http-endpoint">
+ <realm>master</realm>
+ <resource>http-endpoint</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="jwt">
+ <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
+ </credential>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
adapters/saml/as7-eap6/adapter/pom.xml 97(+97 -0)
diff --git a/adapters/saml/as7-eap6/adapter/pom.xml b/adapters/saml/as7-eap6/adapter/pom.xml
new file mode 100755
index 0000000..a5da58d
--- /dev/null
+++ b/adapters/saml/as7-eap6/adapter/pom.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-as7-adapter</artifactId>
+ <name>Keycloak SAML AS7 Integration</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-as7-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.web</groupId>
+ <artifactId>jbossweb</artifactId>
+ <version>7.0.17.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-web</artifactId>
+ <version>7.1.2.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/SamlAuthenticatorValve.java b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/SamlAuthenticatorValve.java
new file mode 100755
index 0000000..17ad74b
--- /dev/null
+++ b/adapters/saml/as7-eap6/adapter/src/main/java/org/keycloak/adapters/saml/jbossweb/SamlAuthenticatorValve.java
@@ -0,0 +1,57 @@
+package org.keycloak.adapters.saml.jbossweb;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.keycloak.adapters.jbossweb.JBossWebPrincipalFactory;
+import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
+ public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws java.io.IOException {
+ return authenticateInternal(request, response, config);
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ forwardToErrorPage(request, (Response)response, config);
+ return true;
+ }
+
+ @Override
+ protected void forwardToLogoutPage(Request request, HttpServletResponse response, SamlDeployment deployment) {
+ super.forwardToLogoutPage(request, response, deployment);
+ }
+
+ @Override
+ public void start() throws LifecycleException {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ super.start();
+ }
+
+
+ public void logout(Request request) {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new JBossWebPrincipalFactory();
+ }
+}
adapters/saml/as7-eap6/pom.xml 20(+20 -0)
diff --git a/adapters/saml/as7-eap6/pom.xml b/adapters/saml/as7-eap6/pom.xml
new file mode 100755
index 0000000..9106fef
--- /dev/null
+++ b/adapters/saml/as7-eap6/pom.xml
@@ -0,0 +1,20 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML EAP Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-eap-integration-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>adapter</module>
+ <module>subsystem</module>
+ </modules>
+</project>
adapters/saml/as7-eap6/subsystem/pom.xml 115(+115 -0)
diff --git a/adapters/saml/as7-eap6/subsystem/pom.xml b/adapters/saml/as7-eap6/subsystem/pom.xml
new file mode 100755
index 0000000..c425806
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-saml-as7-subsystem</artifactId>
+ <name>Keycloak SAML AS7 Subsystem</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.8.1</version>
+ <configuration>
+ <redirectTestOutputToFile>false</redirectTestOutputToFile>
+ <enableAssertions>true</enableAssertions>
+ <argLine>-Xmx512m</argLine>
+ <systemProperties>
+ <property>
+ <name>jboss.home</name>
+ <value>${jboss.home}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ <forkMode>once</forkMode>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-as7-adapter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-naming</artifactId>
+ <version>${jboss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-server</artifactId>
+ <version>${jboss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-ee</artifactId>
+ <version>${jboss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-web</artifactId>
+ <version>${jboss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>3.1.0.GA</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-processor</artifactId>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ <version>1.0.0.Final</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.as</groupId>
+ <artifactId>jboss-as-controller</artifactId>
+ <version>${jboss.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Configuration.java
new file mode 100644
index 0000000..966cd67
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Constants.java
new file mode 100644
index 0000000..07af4f7
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderAddHandler.java
new file mode 100644
index 0000000..679658b
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/IdentityProviderDefinition.java
new file mode 100644
index 0000000..09262f9
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyAddHandler.java
new file mode 100644
index 0000000..b362d4f
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java
new file mode 100755
index 0000000..68e4679
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakAdapterConfigDeploymentProcessor.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.saml.as7;
+
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.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;
+
+/**
+ * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
+ protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class);
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+ String deploymentName = deploymentUnit.getName();
+
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ return;
+ }
+
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ // otherwise
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+
+ 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);
+ }
+
+ 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) {
+ List<ValveMetaData> valves = webMetaData.getValves();
+ if (valves == null) {
+ valves = new ArrayList<ValveMetaData>(1);
+ webMetaData.setValves(valves);
+ }
+ ValveMetaData valve = new ValveMetaData();
+ valve.setValveClass(SamlAuthenticatorValve.class.getName());
+ valve.setModule("org.keycloak.keycloak-saml-as7-adapter");
+ //log.info("******* adding Keycloak valve to: " + deploymentName);
+ valves.add(valve);
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java
new file mode 100755
index 0000000..c214774
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessor.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ // Next phase, need to detect if this is a Keycloak deployment. If not, don't add the modules.
+
+ final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+ final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+ addCommonModules(moduleSpecification, moduleLoader);
+ addPlatformSpecificModules(moduleSpecification, moduleLoader);
+ }
+
+ private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
+ }
+
+ abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessorAS7.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessorAS7.java
new file mode 100755
index 0000000..700fc82
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakDependencyProcessorAS7.java
@@ -0,0 +1,19 @@
+package org.keycloak.subsystem.saml.as7;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>
+ */
+public class KeycloakDependencyProcessorAS7 extends KeycloakDependencyProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_AS7_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-as7-adapter");
+
+ @Override
+ protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_AS7_ADAPTER, false, false, true, false));
+ }}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java
new file mode 100755
index 0000000..d936982
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSamlExtension.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.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;
+import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
+import org.jboss.as.controller.parsing.ExtensionParsingContext;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
+
+
+/**
+ * Main Extension class for the subsystem.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSamlExtension implements Extension {
+
+ public static final String SUBSYSTEM_NAME = "keycloak-saml";
+ 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 ModelVersion MGMT_API_VERSION = ModelVersion.create(1, 1, 0);
+ static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+
+ public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
+ StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
+ for (String kp : keyPrefix) {
+ prefix.append('.').append(kp);
+ }
+ return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakSamlExtension.class.getClassLoader(), true, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeParsers(final ExtensionParsingContext context) {
+ context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakSamlExtension.NAMESPACE, PARSER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(final ExtensionContext context) {
+ 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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
new file mode 100755
index 0000000..eda678f
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemAdd.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.saml.as7;
+
+import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+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;
+
+import java.util.List;
+
+/**
+ * The Keycloak subsystem add update handler.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
+
+ static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
+
+ @Override
+ protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) {
+ context.addStep(new AbstractDeploymentChainStep() {
+ @Override
+ protected void execute(DeploymentProcessorTarget processorTarget) {
+ 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
+ chooseConfigDeploymentProcessor());
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorAS7();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
+ }
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java
new file mode 100755
index 0000000..4b7ef3f
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeycloakSubsystemDefinition.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.saml.as7;
+
+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;
+
+/**
+ * Definition of subsystem=keycloak-saml.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
+
+ static final KeycloakSubsystemDefinition INSTANCE = new KeycloakSubsystemDefinition();
+
+ private KeycloakSubsystemDefinition() {
+ super(KeycloakSamlExtension.SUBSYSTEM_PATH,
+ KeycloakSamlExtension.getResourceDescriptionResolver("subsystem"),
+ KeycloakSubsystemAdd.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE
+ );
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyDefinition.java
new file mode 100644
index 0000000..0f76399
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreCertificateDefinition.java
new file mode 100644
index 0000000..7ae2ff1
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStoreDefinition.java
new file mode 100644
index 0000000..2fb14f5
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/KeyStorePrivateKeyDefinition.java
new file mode 100644
index 0000000..1947ca9
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakLogger.java
new file mode 100755
index 0000000..8bd7ac1
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/logging/KeycloakMessages.java
new file mode 100755
index 0000000..f4917b1
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentAddHandler.java
new file mode 100644
index 0000000..c5325f6
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SecureDeploymentDefinition.java
new file mode 100644
index 0000000..75f4dca
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderAddHandler.java
new file mode 100644
index 0000000..33d6015
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/ServiceProviderDefinition.java
new file mode 100644
index 0000000..02ecc8f
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleLogoutDefinition.java
new file mode 100644
index 0000000..beb884c
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/SingleSignOnDefinition.java
new file mode 100644
index 0000000..827be9b
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Util.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Util.java
new file mode 100755
index 0000000..e73f338
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/Util.java
@@ -0,0 +1,42 @@
+package org.keycloak.subsystem.saml.as7;
+
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
+import org.jboss.dmr.ModelNode;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Util {
+ public static ModelNode createAddOperation(final PathAddress address) {
+ return createOperation(ModelDescriptionConstants.ADD, address);
+ }
+
+ public static ModelNode createAddOperation() {
+ return createEmptyOperation(ModelDescriptionConstants.ADD, null);
+ }
+
+ public static ModelNode createRemoveOperation(final PathAddress address) {
+ return createOperation(ModelDescriptionConstants.REMOVE, address);
+ }
+
+ public static ModelNode createOperation(final String operationName, final PathAddress address) {
+ return createEmptyOperation(operationName, address);
+ }
+
+ public static ModelNode createEmptyOperation(String operationName, final PathAddress address) {
+ ModelNode op = new ModelNode();
+ op.get(OP).set(operationName);
+ if (address != null) {
+ op.get(OP_ADDR).set(address.toModelNode());
+ } else {
+ // Just establish the standard structure; caller can fill in address later
+ op.get(OP_ADDR);
+ }
+ return op;
+ }
+}
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/FormattingXMLStreamWriter.java
new file mode 100644
index 0000000..dda3ed3
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java b/adapters/saml/as7-eap6/subsystem/src/main/java/org/keycloak/subsystem/saml/as7/xml/Spliterator.java
new file mode 100644
index 0000000..e9cb5c6
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/adapters/saml/as7-eap6/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100755
index 0000000..2fd653a
--- /dev/null
+++ b/adapters/saml/as7-eap6/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.saml.as7.KeycloakSamlExtension
diff --git a/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties b/adapters/saml/as7-eap6/subsystem/src/main/resources/org/keycloak/subsystem/saml/as7/LocalDescriptions.properties
new file mode 100755
index 0000000..f8a4a11
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd b/adapters/saml/as7-eap6/subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
new file mode 100644
index 0000000..725104b
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/as7-eap6/subsystem/src/test/resources/org/keycloak/subsystem/saml/as7/keycloak-saml-1.1.xml b/adapters/saml/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/adapters/saml/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/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
new file mode 100755
index 0000000..2496b7c
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/AbstractInitiateLogin.java
@@ -0,0 +1,91 @@
+package org.keycloak.adapters.saml;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.SAML2NameIDPolicyBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import java.io.IOException;
+import java.security.KeyPair;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractInitiateLogin implements AuthChallenge {
+ protected static Logger log = Logger.getLogger(AbstractInitiateLogin.class);
+
+ protected SamlDeployment deployment;
+ protected SamlSessionStore sessionStore;
+
+ public AbstractInitiateLogin(SamlDeployment deployment, SamlSessionStore sessionStore) {
+ this.deployment = deployment;
+ this.sessionStore = sessionStore;
+ }
+
+ @Override
+ public int getResponseCode() {
+ return 0;
+ }
+
+ @Override
+ public boolean challenge(HttpFacade httpFacade) {
+ try {
+ String issuerURL = deployment.getEntityID();
+ String nameIDPolicyFormat = deployment.getNameIDPolicyFormat();
+
+ if (nameIDPolicyFormat == null) {
+ nameIDPolicyFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+ }
+
+ SAML2AuthnRequestBuilder authnRequestBuilder = new SAML2AuthnRequestBuilder()
+ .destination(deployment.getIDP().getSingleSignOnService().getRequestBindingUrl())
+ .issuer(issuerURL)
+ .forceAuthn(deployment.isForceAuthentication()).isPassive(deployment.isIsPassive())
+ .nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
+ if (deployment.getIDP().getSingleSignOnService().getResponseBinding() != null) {
+ String protocolBinding = JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get();
+ if (deployment.getIDP().getSingleSignOnService().getResponseBinding() == SamlDeployment.Binding.POST) {
+ protocolBinding = JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get();
+ }
+ authnRequestBuilder.protocolBinding(protocolBinding);
+
+ }
+ if (deployment.getAssertionConsumerServiceUrl() != null) {
+ authnRequestBuilder.assertionConsumerUrl(deployment.getAssertionConsumerServiceUrl());
+ }
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+
+ if (deployment.getIDP().getSingleSignOnService().signRequest()) {
+
+
+ KeyPair keypair = deployment.getSigningKeyPair();
+ if (keypair == null) {
+ throw new RuntimeException("Signing keys not configured");
+ }
+ if (deployment.getSignatureCanonicalizationMethod() != null) {
+ binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+ }
+
+ binding.signWith(keypair);
+ binding.signDocument();
+ }
+ sessionStore.saveRequest();
+
+ sendAuthnRequest(httpFacade, authnRequestBuilder, binding);
+ sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_IN);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not create authentication request.", e);
+ }
+ return true;
+ }
+
+ protected abstract void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) throws ProcessingException, ConfigurationException, IOException;
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
new file mode 100755
index 0000000..956ab86
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/IDP.java
@@ -0,0 +1,191 @@
+package org.keycloak.adapters.saml.config;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class IDP implements Serializable {
+ public static class SingleSignOnService implements Serializable {
+ private boolean signRequest;
+ private boolean validateResponseSignature;
+ private String requestBinding;
+ private String responseBinding;
+ private String bindingUrl;
+
+ public boolean isSignRequest() {
+ return signRequest;
+ }
+
+ public void setSignRequest(boolean signRequest) {
+ this.signRequest = signRequest;
+ }
+
+ public boolean isValidateResponseSignature() {
+ return validateResponseSignature;
+ }
+
+ public void setValidateResponseSignature(boolean validateResponseSignature) {
+ this.validateResponseSignature = validateResponseSignature;
+ }
+
+ public String getRequestBinding() {
+ return requestBinding;
+ }
+
+ public void setRequestBinding(String requestBinding) {
+ this.requestBinding = requestBinding;
+ }
+
+ public String getResponseBinding() {
+ return responseBinding;
+ }
+
+ public void setResponseBinding(String responseBinding) {
+ this.responseBinding = responseBinding;
+ }
+
+ public String getBindingUrl() {
+ return bindingUrl;
+ }
+
+ public void setBindingUrl(String bindingUrl) {
+ this.bindingUrl = bindingUrl;
+ }
+ }
+
+ public static class SingleLogoutService implements Serializable {
+ private boolean signRequest;
+ private boolean signResponse;
+ private boolean validateRequestSignature;
+ private boolean validateResponseSignature;
+ private String requestBinding;
+ private String responseBinding;
+ private String postBindingUrl;
+ private String redirectBindingUrl;
+
+ public boolean isSignRequest() {
+ return signRequest;
+ }
+
+ public void setSignRequest(boolean signRequest) {
+ this.signRequest = signRequest;
+ }
+
+ public boolean isSignResponse() {
+ return signResponse;
+ }
+
+ public void setSignResponse(boolean signResponse) {
+ this.signResponse = signResponse;
+ }
+
+ public boolean isValidateRequestSignature() {
+ return validateRequestSignature;
+ }
+
+ public void setValidateRequestSignature(boolean validateRequestSignature) {
+ this.validateRequestSignature = validateRequestSignature;
+ }
+
+ public boolean isValidateResponseSignature() {
+ return validateResponseSignature;
+ }
+
+ public void setValidateResponseSignature(boolean validateResponseSignature) {
+ this.validateResponseSignature = validateResponseSignature;
+ }
+
+ public String getRequestBinding() {
+ return requestBinding;
+ }
+
+ public void setRequestBinding(String requestBinding) {
+ this.requestBinding = requestBinding;
+ }
+
+ public String getResponseBinding() {
+ return responseBinding;
+ }
+
+ public void setResponseBinding(String responseBinding) {
+ this.responseBinding = responseBinding;
+ }
+
+ public String getPostBindingUrl() {
+ return postBindingUrl;
+ }
+
+ public void setPostBindingUrl(String postBindingUrl) {
+ this.postBindingUrl = postBindingUrl;
+ }
+
+ public String getRedirectBindingUrl() {
+ return redirectBindingUrl;
+ }
+
+ public void setRedirectBindingUrl(String redirectBindingUrl) {
+ this.redirectBindingUrl = redirectBindingUrl;
+ }
+ }
+
+ private String entityID;
+ private String signatureAlgorithm;
+ private String signatureCanonicalizationMethod;
+ private SingleSignOnService singleSignOnService;
+ private SingleLogoutService singleLogoutService;
+ private List<Key> keys;
+
+ public String getEntityID() {
+ return entityID;
+ }
+
+ public void setEntityID(String entityID) {
+ this.entityID = entityID;
+ }
+
+ public SingleSignOnService getSingleSignOnService() {
+ return singleSignOnService;
+ }
+
+ public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
+ this.singleSignOnService = singleSignOnService;
+ }
+
+ public SingleLogoutService getSingleLogoutService() {
+ return singleLogoutService;
+ }
+
+ public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
+ this.singleLogoutService = singleLogoutService;
+ }
+
+ public List<Key> getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List<Key> keys) {
+ this.keys = keys;
+ }
+
+ public String getSignatureAlgorithm() {
+ return signatureAlgorithm;
+ }
+
+ public void setSignatureAlgorithm(String signatureAlgorithm) {
+ this.signatureAlgorithm = signatureAlgorithm;
+ }
+
+ public String getSignatureCanonicalizationMethod() {
+ return signatureCanonicalizationMethod;
+ }
+
+ public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
+ this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/Key.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/Key.java
new file mode 100755
index 0000000..3d94f78
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/Key.java
@@ -0,0 +1,143 @@
+package org.keycloak.adapters.saml.config;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class Key implements Serializable {
+
+ public static class KeyStoreConfig implements Serializable {
+ private String file;
+ private String resource;
+ private String password;
+ private String type;
+ private String alias;
+ private String privateKeyAlias;
+ private String privateKeyPassword;
+ private String certificateAlias;
+
+
+ public String getFile() {
+ return file;
+ }
+
+ public void setFile(String file) {
+ this.file = file;
+ }
+
+ public String getResource() {
+ return resource;
+ }
+
+ public void setResource(String resource) {
+ this.resource = resource;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getPrivateKeyAlias() {
+ return privateKeyAlias;
+ }
+
+ public void setPrivateKeyAlias(String privateKeyAlias) {
+ this.privateKeyAlias = privateKeyAlias;
+ }
+
+ public String getPrivateKeyPassword() {
+ return privateKeyPassword;
+ }
+
+ public void setPrivateKeyPassword(String privateKeyPassword) {
+ this.privateKeyPassword = privateKeyPassword;
+ }
+
+ public String getCertificateAlias() {
+ return certificateAlias;
+ }
+
+ public void setCertificateAlias(String certificateAlias) {
+ this.certificateAlias = certificateAlias;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+ }
+
+
+ private boolean signing;
+ private boolean encryption;
+ private KeyStoreConfig keystore;
+ private String privateKeyPem;
+ private String publicKeyPem;
+ private String certificatePem;
+
+
+ public boolean isSigning() {
+ return signing;
+ }
+
+ public void setSigning(boolean signing) {
+ this.signing = signing;
+ }
+
+ public boolean isEncryption() {
+ return encryption;
+ }
+
+ public void setEncryption(boolean encryption) {
+ this.encryption = encryption;
+ }
+
+ public KeyStoreConfig getKeystore() {
+ return keystore;
+ }
+
+ public void setKeystore(KeyStoreConfig keystore) {
+ this.keystore = keystore;
+ }
+
+ public String getPrivateKeyPem() {
+ return privateKeyPem;
+ }
+
+ public void setPrivateKeyPem(String privateKeyPem) {
+ this.privateKeyPem = privateKeyPem;
+ }
+
+ public String getPublicKeyPem() {
+ return publicKeyPem;
+ }
+
+ public void setPublicKeyPem(String publicKeyPem) {
+ this.publicKeyPem = publicKeyPem;
+ }
+
+ public String getCertificatePem() {
+ return certificatePem;
+ }
+
+ public void setCertificatePem(String certificatePem) {
+ this.certificatePem = certificatePem;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java
new file mode 100755
index 0000000..9370dba
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/KeycloakSamlAdapter.java
@@ -0,0 +1,21 @@
+package org.keycloak.adapters.saml.config;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapter implements Serializable {
+ private List<SP> sps = new LinkedList<>();
+
+ public List<SP> getSps() {
+ return sps;
+ }
+
+ public void setSps(List<SP> sps) {
+ this.sps = sps;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
new file mode 100755
index 0000000..91a016c
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/ConfigXmlConstants.java
@@ -0,0 +1,56 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ConfigXmlConstants {
+ public static final String KEYCLOAK_SAML_ADAPTER = "keycloak-saml-adapter";
+ public static final String SP_ELEMENT = "SP";
+ public static final String ENTITY_ID_ATTR = "entityID";
+ public static final String SSL_POLICY_ATTR = "sslPolicy";
+ public static final String NAME_ID_POLICY_FORMAT_ATTR = "nameIDPolicyFormat";
+ public static final String FORCE_AUTHENTICATION_ATTR = "forceAuthentication";
+ public static final String IS_PASSIVE_ATTR = "isPassive";
+ public static final String SIGNATURE_ALGORITHM_ATTR = "signatureAlgorithm";
+ public static final String SIGNATURE_CANONICALIZATION_METHOD_ATTR = "signatureCanonicalizationMethod";
+ public static final String LOGOUT_PAGE_ATTR = "logoutPage";
+
+ public static final String KEYS_ELEMENT = "Keys";
+ public static final String KEY_ELEMENT = "Key";
+ public static final String SIGNING_ATTR = "signing";
+ public static final String ENCRYPTION_ATTR = "encryption";
+ public static final String CERTIFICATE_PEM_ELEMENT = "CertificatePem";
+ public static final String PRIVATE_KEY_PEM_ELEMENT = "PrivateKeyPem";
+ public static final String PUBLIC_KEY_PEM_ELEMENT = "PublicKeyPem";
+ public static final String FILE_ATTR = "file";
+ public static final String TYPE_ATTR = "type";
+ public static final String RESOURCE_ATTR = "resource";
+ public static final String PASSWORD_ATTR = "password";
+ public static final String ALIAS_ATTR = "alias";
+ public static final String KEYS_STORE_ELEMENT = "KeyStore";
+ public static final String CERTIFICATE_ELEMENT = "Certificate";
+ public static final String PRIVATE_KEY_ELEMENT = "PrivateKey";
+
+ public static final String PRINCIPAL_NAME_MAPPING_ELEMENT = "PrincipalNameMapping";
+ public static final String POLICY_ATTR = "policy";
+ public static final String ATTRIBUTE_ATTR = "attribute";
+
+ public static final String ROLE_IDENTIFIERS_ELEMENT = "RoleIdentifiers";
+ public static final String ATTRIBUTE_ELEMENT = "Attribute";
+ public static final String NAME_ATTR = "name";
+
+ public static final String IDP_ELEMENT = "IDP";
+ public static final String SIGNATURES_REQUIRED_ATTR = "signaturesRequired";
+ public static final String SINGLE_SIGN_ON_SERVICE_ELEMENT = "SingleSignOnService";
+ public static final String SINGLE_LOGOUT_SERVICE_ELEMENT = "SingleLogoutService";
+ public static final String SIGN_REQUEST_ATTR = "signRequest";
+ public static final String SIGN_RESPONSE_ATTR = "signResponse";
+ public static final String REQUEST_BINDING_ATTR = "requestBinding";
+ public static final String RESPONSE_BINDING_ATTR = "responseBinding";
+ public static final String BINDING_URL_ATTR = "bindingUrl";
+ public static final String VALIDATE_RESPONSE_SIGNATURE_ATTR = "validateResponseSignature";
+ public static final String VALIDATE_REQUEST_SIGNATURE_ATTR = "validateRequestSignature";
+ public static final String POST_BINDING_URL_ATTR = "postBindingUrl";
+ public static final String REDIRECT_BINDING_URL_ATTR = "redirectBindingUrl";
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
new file mode 100755
index 0000000..e0564e9
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/DeploymentBuilder.java
@@ -0,0 +1,227 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.common.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.common.util.PemUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DeploymentBuilder {
+ public SamlDeployment build(InputStream xml, ResourceLoader resourceLoader) throws ParsingException {
+ DefaultSamlDeployment deployment = new DefaultSamlDeployment();
+ DefaultSamlDeployment.DefaultIDP idp = new DefaultSamlDeployment.DefaultIDP();
+ DefaultSamlDeployment.DefaultSingleSignOnService sso = new DefaultSamlDeployment.DefaultSingleSignOnService();
+ DefaultSamlDeployment.DefaultSingleLogoutService slo = new DefaultSamlDeployment.DefaultSingleLogoutService();
+ idp.setSingleSignOnService(sso);
+ idp.setSingleLogoutService(slo);
+
+ KeycloakSamlAdapter adapter = (KeycloakSamlAdapter)(new KeycloakSamlAdapterXMLParser().parse(xml));
+ SP sp = adapter.getSps().get(0);
+ deployment.setConfigured(true);
+ deployment.setEntityID(sp.getEntityID());
+ deployment.setForceAuthentication(sp.isForceAuthentication());
+ deployment.setIsPassive(sp.isIsPassive());
+ deployment.setNameIDPolicyFormat(sp.getNameIDPolicyFormat());
+ deployment.setLogoutPage(sp.getLogoutPage());
+ deployment.setSignatureCanonicalizationMethod(sp.getIdp().getSignatureCanonicalizationMethod());
+ deployment.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
+ if (sp.getIdp().getSignatureAlgorithm() != null) {
+ deployment.setSignatureAlgorithm(SignatureAlgorithm.valueOf(sp.getIdp().getSignatureAlgorithm()));
+ }
+ if (sp.getPrincipalNameMapping() != null) {
+ SamlDeployment.PrincipalNamePolicy policy = SamlDeployment.PrincipalNamePolicy.valueOf(sp.getPrincipalNameMapping().getPolicy());
+ deployment.setPrincipalNamePolicy(policy);
+ deployment.setPrincipalAttributeName(sp.getPrincipalNameMapping().getAttributeName());
+ }
+ deployment.setRoleAttributeNames(sp.getRoleAttributes());
+ if (sp.getRoleAttributes() == null) {
+ Set<String> roles = new HashSet<>();
+ roles.add("Role");
+ deployment.setRoleAttributeNames(roles);
+ }
+ if (sp.getSslPolicy() != null) {
+ SslRequired ssl = SslRequired.valueOf(sp.getSslPolicy());
+ deployment.setSslRequired(ssl);
+ }
+ if (sp.getKeys() != null) {
+ for (Key key : sp.getKeys()) {
+ if (key.isSigning()) {
+ PrivateKey privateKey = null;
+ PublicKey publicKey = null;
+ if (key.getKeystore() != null) {
+ KeyStore keyStore = loadKeystore(resourceLoader, key);
+ Certificate cert = null;
+ try {
+ cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+ privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ publicKey = cert.getPublicKey();
+ } else {
+ if (key.getPrivateKeyPem() == null) {
+ throw new RuntimeException("SP signing key must have a PrivateKey defined");
+ }
+ try {
+ privateKey = PemUtils.decodePrivateKey(key.getPrivateKeyPem().trim());
+ if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+ throw new RuntimeException("Sp signing key must have a PublicKey or Certificate defined");
+ }
+ publicKey = getPublicKeyFromPem(key);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ KeyPair keyPair = new KeyPair(publicKey, privateKey);
+ deployment.setSigningKeyPair(keyPair);
+
+ }
+ if (key.isEncryption()) {
+ if (key.getKeystore() != null) {
+
+ KeyStore keyStore = loadKeystore(resourceLoader, key);
+ try {
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(key.getKeystore().getPrivateKeyAlias(), key.getKeystore().getPrivateKeyPassword().toCharArray());
+ deployment.setDecryptionKey(privateKey);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ if (key.getPrivateKeyPem() == null) {
+ throw new RuntimeException("SP signing key must have a PrivateKey defined");
+ }
+ try {
+ PrivateKey privateKey = PemUtils.decodePrivateKey(key.getPrivateKeyPem().trim());
+ deployment.setDecryptionKey(privateKey);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+ }
+ }
+ }
+
+ deployment.setIdp(idp);
+ idp.setEntityID(sp.getIdp().getEntityID());
+ sso.setRequestBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getRequestBinding()));
+ sso.setRequestBindingUrl(sp.getIdp().getSingleSignOnService().getBindingUrl());
+ if (sp.getIdp().getSingleSignOnService().getResponseBinding() != null) {
+ sso.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleSignOnService().getResponseBinding()));
+ }
+ sso.setSignRequest(sp.getIdp().getSingleSignOnService().isSignRequest());
+ sso.setValidateResponseSignature(sp.getIdp().getSingleSignOnService().isValidateResponseSignature());
+
+ slo.setSignRequest(sp.getIdp().getSingleLogoutService().isSignRequest());
+ slo.setSignResponse(sp.getIdp().getSingleLogoutService().isSignResponse());
+ slo.setValidateResponseSignature(sp.getIdp().getSingleLogoutService().isValidateResponseSignature());
+ slo.setValidateRequestSignature(sp.getIdp().getSingleLogoutService().isValidateRequestSignature());
+ slo.setRequestBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleLogoutService().getRequestBinding()));
+ slo.setResponseBinding(SamlDeployment.Binding.parseBinding(sp.getIdp().getSingleLogoutService().getResponseBinding()));
+ if (slo.getRequestBinding() == SamlDeployment.Binding.POST) {
+ slo.setRequestBindingUrl(sp.getIdp().getSingleLogoutService().getPostBindingUrl());
+ } else {
+ slo.setRequestBindingUrl(sp.getIdp().getSingleLogoutService().getRedirectBindingUrl());
+ }
+ if (slo.getResponseBinding() == SamlDeployment.Binding.POST) {
+ slo.setResponseBindingUrl(sp.getIdp().getSingleLogoutService().getPostBindingUrl());
+ } else {
+ slo.setResponseBindingUrl(sp.getIdp().getSingleLogoutService().getRedirectBindingUrl());
+ }
+ if (sp.getIdp().getKeys() != null) {
+ for (Key key : sp.getIdp().getKeys()) {
+ if (key.isSigning()) {
+ if (key.getKeystore() != null) {
+ KeyStore keyStore = loadKeystore(resourceLoader, key);
+ Certificate cert = null;
+ try {
+ cert = keyStore.getCertificate(key.getKeystore().getCertificateAlias());
+ } catch (KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ idp.setSignatureValidationKey(cert.getPublicKey());
+ } else {
+ if (key.getPublicKeyPem() == null && key.getCertificatePem() == null) {
+ throw new RuntimeException("IDP signing key must have a PublicKey or Certificate defined");
+ }
+ try {
+ PublicKey publicKey = getPublicKeyFromPem(key);
+ idp.setSignatureValidationKey(publicKey);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+ }
+
+
+ return deployment;
+ }
+
+ protected static PublicKey getPublicKeyFromPem(Key key) throws Exception {
+ PublicKey publicKey;
+ if (key.getPublicKeyPem() != null) {
+ publicKey = PemUtils.decodePublicKey(key.getPublicKeyPem().trim());
+ } else {
+ Certificate cert = PemUtils.decodeCertificate(key.getCertificatePem().trim());
+ publicKey = cert.getPublicKey();
+ }
+ return publicKey;
+ }
+
+ protected static KeyStore loadKeystore(ResourceLoader resourceLoader, Key key) {
+ String type = key.getKeystore().getType();
+ if (type == null) type = "JKS";
+ KeyStore keyStore = null;
+ try {
+ keyStore = KeyStore.getInstance(type);
+ } catch (KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+ InputStream is = null;
+ if (key.getKeystore().getFile() != null) {
+ File fp = new File(key.getKeystore().getFile());
+ if (!fp.exists()) {
+ }
+ try {
+ is = new FileInputStream(fp);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("KeyStore " + key.getKeystore().getFile() + " does not exist");
+ }
+
+ } else {
+ is = resourceLoader.getResourceAsStream(key.getKeystore().getResource());
+ if (is == null) {
+ throw new RuntimeException("KeyStore " + key.getKeystore().getResource() + " does not exist");
+ }
+ }
+ try {
+ keyStore.load(is, key.getKeystore().getPassword().toCharArray());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return keyStore;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
new file mode 100755
index 0000000..d1aaea9
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/IDPXmlParser.java
@@ -0,0 +1,102 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class IDPXmlParser extends AbstractParser {
+
+ @Override
+ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.IDP_ELEMENT);
+ IDP idp = new IDP();
+ String entityID = SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+ if (entityID == null) {
+ throw new ParsingException("entityID must be set on IDP");
+
+ }
+ idp.setEntityID(entityID);
+
+ boolean signaturesRequired = SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNATURES_REQUIRED_ATTR);
+ idp.setSignatureCanonicalizationMethod(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_CANONICALIZATION_METHOD_ATTR));
+ idp.setSignatureAlgorithm(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.SIGNATURE_ALGORITHM_ATTR));
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.IDP_ELEMENT))
+ break;
+ else
+ continue;
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.SINGLE_SIGN_ON_SERVICE_ELEMENT)) {
+ IDP.SingleSignOnService sso = parseSingleSignOnService(xmlEventReader, signaturesRequired);
+ idp.setSingleSignOnService(sso);
+
+ } else if (tag.equals(ConfigXmlConstants.SINGLE_LOGOUT_SERVICE_ELEMENT)) {
+ IDP.SingleLogoutService slo = parseSingleLogoutService(xmlEventReader, signaturesRequired);
+ idp.setSingleLogoutService(slo);
+
+ } else if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
+ KeysXmlParser parser = new KeysXmlParser();
+ List<Key> keys = (List<Key>)parser.parse(xmlEventReader);
+ idp.setKeys(keys);
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return idp;
+ }
+
+ protected IDP.SingleLogoutService parseSingleLogoutService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
+ IDP.SingleLogoutService slo = new IDP.SingleLogoutService();
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ slo.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+ slo.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+ slo.setValidateRequestSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_REQUEST_SIGNATURE_ATTR, signaturesRequired));
+ slo.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+ slo.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+ slo.setSignResponse(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_RESPONSE_ATTR, signaturesRequired));
+ slo.setPostBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.POST_BINDING_URL_ATTR));
+ slo.setRedirectBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REDIRECT_BINDING_URL_ATTR));
+ return slo;
+ }
+
+ protected IDP.SingleSignOnService parseSingleSignOnService(XMLEventReader xmlEventReader, boolean signaturesRequired) throws ParsingException {
+ IDP.SingleSignOnService sso = new IDP.SingleSignOnService();
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ sso.setSignRequest(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.SIGN_REQUEST_ATTR, signaturesRequired));
+ sso.setValidateResponseSignature(SPXmlParser.getBooleanAttributeValue(element, ConfigXmlConstants.VALIDATE_RESPONSE_SIGNATURE_ATTR, signaturesRequired));
+ sso.setRequestBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.REQUEST_BINDING_ATTR));
+ sso.setResponseBinding(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.RESPONSE_BINDING_ATTR));
+ sso.setBindingUrl(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.BINDING_URL_ATTR));
+ return sso;
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ return false;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java
new file mode 100755
index 0000000..d5b9ee6
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeycloakSamlAdapterXMLParser.java
@@ -0,0 +1,46 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.StartElement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAdapterXMLParser extends AbstractParser {
+
+ @Override
+ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+ KeycloakSamlAdapter adapter = new KeycloakSamlAdapter();
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYCLOAK_SAML_ADAPTER);
+ while (xmlEventReader.hasNext()) {
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.SP_ELEMENT)) {
+ SPXmlParser parser = new SPXmlParser();
+ SP sp = (SP)parser.parse(xmlEventReader);
+ if (sp != null) adapter.getSps().add(sp);
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return adapter;
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ return false;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java
new file mode 100755
index 0000000..a63a6ac
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeysXmlParser.java
@@ -0,0 +1,60 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeysXmlParser extends AbstractParser {
+
+ @Override
+ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYS_ELEMENT);
+ List<Key> keys = new LinkedList<>();
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.KEYS_ELEMENT))
+ break;
+ else
+ throw logger.parserUnknownEndElement(endElementName);
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.KEY_ELEMENT)) {
+ KeyXmlParser parser = new KeyXmlParser();
+ Key key = (Key)parser.parse(xmlEventReader);
+ keys.add(key);
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return keys;
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ return false;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java
new file mode 100755
index 0000000..0308a56
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/KeyXmlParser.java
@@ -0,0 +1,128 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeyXmlParser extends AbstractParser {
+
+ @Override
+ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.KEY_ELEMENT);
+ Key key = new Key();
+ key.setSigning(SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.SIGNING_ATTR));
+ key.setEncryption(SPXmlParser.getBooleanAttributeValue(startElement, ConfigXmlConstants.ENCRYPTION_ATTR));
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.KEY_ELEMENT))
+ break;
+ else
+ throw logger.parserUnknownEndElement(endElementName);
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.KEYS_STORE_ELEMENT)) {
+ key.setKeystore(parseKeyStore(xmlEventReader));
+ } else if (tag.equals(ConfigXmlConstants.CERTIFICATE_PEM_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ key.setCertificatePem(SPXmlParser.getElementText(xmlEventReader));
+ } else if (tag.equals(ConfigXmlConstants.PUBLIC_KEY_PEM_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ key.setPublicKeyPem(SPXmlParser.getElementText(xmlEventReader));
+ } else if (tag.equals(ConfigXmlConstants.PRIVATE_KEY_PEM_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ key.setPrivateKeyPem(SPXmlParser.getElementText(xmlEventReader));
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return key;
+ }
+
+ protected Key.KeyStoreConfig parseKeyStore(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.KEYS_STORE_ELEMENT);
+ Key.KeyStoreConfig keyStore = new Key.KeyStoreConfig();
+ keyStore.setType(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.TYPE_ATTR));
+ keyStore.setAlias(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.ALIAS_ATTR));
+ keyStore.setFile(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.FILE_ATTR));
+ keyStore.setResource(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.RESOURCE_ATTR));
+ if (keyStore.getFile() == null && keyStore.getResource() == null) {
+ throw new ParsingException("KeyStore element must have the url or classpath attribute set");
+ }
+ keyStore.setPassword(SPXmlParser.getAttributeValue(startElement, ConfigXmlConstants.PASSWORD_ATTR));
+ if (keyStore.getPassword() == null) {
+ throw new ParsingException("KeyStore element must have the password attribute set");
+ }
+
+
+
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.KEYS_STORE_ELEMENT))
+ break;
+ else
+ continue;
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.CERTIFICATE_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ keyStore.setCertificateAlias(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.ALIAS_ATTR));
+ if (keyStore.getCertificateAlias() == null) {
+ throw new ParsingException("KeyStore Certificate element must have the alias attribute set");
+
+ }
+ } else if (tag.equals(ConfigXmlConstants.PRIVATE_KEY_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ keyStore.setPrivateKeyAlias(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.ALIAS_ATTR));
+ if (keyStore.getPrivateKeyAlias() == null) {
+ throw new ParsingException("KeyStore PrivateKey element must have the alias attribute set");
+
+ }
+ keyStore.setPrivateKeyPassword(SPXmlParser.getAttributeValue(element, ConfigXmlConstants.PASSWORD_ATTR));
+ if (keyStore.getPrivateKeyPassword() == null) {
+ throw new ParsingException("KeyStore PrivateKey element must have the password attribute set");
+
+ }
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return keyStore;
+
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ return false;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
new file mode 100755
index 0000000..14b04e1
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/parsers/SPXmlParser.java
@@ -0,0 +1,156 @@
+package org.keycloak.adapters.saml.config.parsers;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.events.EndElement;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.common.util.StringPropertyReplacer;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.parsers.AbstractParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SPXmlParser extends AbstractParser {
+
+ public static String getAttributeValue(StartElement startElement, String tag) {
+ String str = StaxParserUtil.getAttributeValue(startElement, tag);
+ if (str != null)
+ return StringPropertyReplacer.replaceProperties(str);
+ else
+ return str;
+ }
+
+ public static boolean getBooleanAttributeValue(StartElement startElement, String tag, boolean defaultValue) {
+ String result = getAttributeValue(startElement, tag);
+ if (result == null)
+ return defaultValue;
+ return Boolean.valueOf(result);
+ }
+
+ public static boolean getBooleanAttributeValue(StartElement startElement, String tag) {
+ return getBooleanAttributeValue(startElement, tag, false);
+ }
+
+ public static String getElementText(XMLEventReader xmlEventReader) throws ParsingException {
+ String result = StaxParserUtil.getElementText(xmlEventReader);
+ if (result != null)
+ result = StringPropertyReplacer.replaceProperties(result);
+ return result;
+ }
+
+ @Override
+ public Object parse(XMLEventReader xmlEventReader) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.SP_ELEMENT);
+ SP sp = new SP();
+ String entityID = getAttributeValue(startElement, ConfigXmlConstants.ENTITY_ID_ATTR);
+ if (entityID == null) {
+ throw new ParsingException("entityID must be set on SP");
+
+ }
+ sp.setEntityID(entityID);
+ sp.setSslPolicy(getAttributeValue(startElement, ConfigXmlConstants.SSL_POLICY_ATTR));
+ sp.setLogoutPage(getAttributeValue(startElement, ConfigXmlConstants.LOGOUT_PAGE_ATTR));
+ sp.setNameIDPolicyFormat(getAttributeValue(startElement, ConfigXmlConstants.NAME_ID_POLICY_FORMAT_ATTR));
+ sp.setForceAuthentication(getBooleanAttributeValue(startElement, ConfigXmlConstants.FORCE_AUTHENTICATION_ATTR));
+ sp.setIsPassive(getBooleanAttributeValue(startElement, ConfigXmlConstants.IS_PASSIVE_ATTR));
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.SP_ELEMENT))
+ break;
+ else
+ continue;
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.KEYS_ELEMENT)) {
+ KeysXmlParser parser = new KeysXmlParser();
+ List<Key> keys = (List<Key>) parser.parse(xmlEventReader);
+ sp.setKeys(keys);
+ } else if (tag.equals(ConfigXmlConstants.PRINCIPAL_NAME_MAPPING_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ String policy = getAttributeValue(element, ConfigXmlConstants.POLICY_ATTR);
+ if (policy == null) {
+ throw new ParsingException("PrincipalNameMapping element must have the policy attribute set");
+
+ }
+ String attribute = getAttributeValue(element, ConfigXmlConstants.ATTRIBUTE_ATTR);
+ SP.PrincipalNameMapping mapping = new SP.PrincipalNameMapping();
+ mapping.setPolicy(policy);
+ mapping.setAttributeName(attribute);
+ sp.setPrincipalNameMapping(mapping);
+
+ } else if (tag.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT)) {
+ parseRoleMapping(xmlEventReader, sp);
+ } else if (tag.equals(ConfigXmlConstants.IDP_ELEMENT)) {
+ IDPXmlParser parser = new IDPXmlParser();
+ IDP idp = (IDP) parser.parse(xmlEventReader);
+ sp.setIdp(idp);
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ return sp;
+ }
+
+ protected void parseRoleMapping(XMLEventReader xmlEventReader, SP sp) throws ParsingException {
+ StartElement startElement = StaxParserUtil.getNextStartElement(xmlEventReader);
+ StaxParserUtil.validate(startElement, ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT);
+ Set<String> roleAttributes = new HashSet<>();
+ while (xmlEventReader.hasNext()) {
+ XMLEvent xmlEvent = StaxParserUtil.peek(xmlEventReader);
+ if (xmlEvent == null)
+ break;
+ if (xmlEvent instanceof EndElement) {
+ EndElement endElement = (EndElement) StaxParserUtil.getNextEvent(xmlEventReader);
+ String endElementName = StaxParserUtil.getEndElementName(endElement);
+ if (endElementName.equals(ConfigXmlConstants.ROLE_IDENTIFIERS_ELEMENT))
+ break;
+ else
+ continue;
+ }
+ startElement = StaxParserUtil.peekNextStartElement(xmlEventReader);
+ if (startElement == null)
+ break;
+ String tag = StaxParserUtil.getStartElementName(startElement);
+ if (tag.equals(ConfigXmlConstants.ATTRIBUTE_ELEMENT)) {
+ StartElement element = StaxParserUtil.getNextStartElement(xmlEventReader);
+ String attributeValue = getAttributeValue(element, ConfigXmlConstants.NAME_ATTR);
+ if (attributeValue == null) {
+ throw new ParsingException("RoleMapping Attribute element must have the name attribute set");
+
+ }
+ roleAttributes.add(attributeValue);
+ } else {
+ StaxParserUtil.bypassElementBlock(xmlEventReader, tag);
+ }
+
+ }
+ sp.setRoleAttributes(roleAttributes);
+ }
+
+ @Override
+ public boolean supports(QName qname) {
+ return false;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
new file mode 100755
index 0000000..5203b13
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/config/SP.java
@@ -0,0 +1,124 @@
+package org.keycloak.adapters.saml.config;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SP implements Serializable {
+ public static class PrincipalNameMapping implements Serializable {
+ private String policy;
+ private String attributeName;
+
+ public String getPolicy() {
+ return policy;
+ }
+
+ public void setPolicy(String policy) {
+ this.policy = policy;
+ }
+
+ public String getAttributeName() {
+ return attributeName;
+ }
+
+ public void setAttributeName(String attributeName) {
+ this.attributeName = attributeName;
+ }
+ }
+
+ private String entityID;
+ private String sslPolicy;
+ private boolean forceAuthentication;
+ private boolean isPassive;
+ private String logoutPage;
+ private List<Key> keys;
+ private String nameIDPolicyFormat;
+ private PrincipalNameMapping principalNameMapping;
+ private Set<String> roleAttributes;
+ private IDP idp;
+
+ public String getEntityID() {
+ return entityID;
+ }
+
+ public void setEntityID(String entityID) {
+ this.entityID = entityID;
+ }
+
+ public String getSslPolicy() {
+ return sslPolicy;
+ }
+
+ public void setSslPolicy(String sslPolicy) {
+ this.sslPolicy = sslPolicy;
+ }
+
+ public boolean isForceAuthentication() {
+ return forceAuthentication;
+ }
+
+ public void setForceAuthentication(boolean forceAuthentication) {
+ this.forceAuthentication = forceAuthentication;
+ }
+
+ public boolean isIsPassive() {
+ return isPassive;
+ }
+
+ public void setIsPassive(boolean isPassive) {
+ this.isPassive = isPassive;
+ }
+
+ public List<Key> getKeys() {
+ return keys;
+ }
+
+ public void setKeys(List<Key> keys) {
+ this.keys = keys;
+ }
+
+ public String getNameIDPolicyFormat() {
+ return nameIDPolicyFormat;
+ }
+
+ public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
+ this.nameIDPolicyFormat = nameIDPolicyFormat;
+ }
+
+ public PrincipalNameMapping getPrincipalNameMapping() {
+ return principalNameMapping;
+ }
+
+ public void setPrincipalNameMapping(PrincipalNameMapping principalNameMapping) {
+ this.principalNameMapping = principalNameMapping;
+ }
+
+ public Set<String> getRoleAttributes() {
+ return roleAttributes;
+ }
+
+ public void setRoleAttributes(Set<String> roleAttributes) {
+ this.roleAttributes = roleAttributes;
+ }
+
+ public IDP getIdp() {
+ return idp;
+ }
+
+ public void setIdp(IDP idp) {
+ this.idp = idp;
+ }
+
+ public String getLogoutPage() {
+ return logoutPage;
+ }
+
+ public void setLogoutPage(String logoutPage) {
+ this.logoutPage = logoutPage;
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
new file mode 100755
index 0000000..7aab095
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/DefaultSamlDeployment.java
@@ -0,0 +1,357 @@
+package org.keycloak.adapters.saml;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Set;
+
+import org.keycloak.common.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class DefaultSamlDeployment implements SamlDeployment {
+
+ public static class DefaultSingleSignOnService implements IDP.SingleSignOnService {
+ private boolean signRequest;
+ private boolean validateResponseSignature;
+ private Binding requestBinding;
+ private Binding responseBinding;
+ private String requestBindingUrl;
+
+ @Override
+ public boolean signRequest() {
+ return signRequest;
+ }
+
+ @Override
+ public boolean validateResponseSignature() {
+ return validateResponseSignature;
+ }
+
+ @Override
+ public Binding getRequestBinding() {
+ return requestBinding;
+ }
+
+ @Override
+ public Binding getResponseBinding() {
+ return responseBinding;
+ }
+
+ @Override
+ public String getRequestBindingUrl() {
+ return requestBindingUrl;
+ }
+
+ public void setSignRequest(boolean signRequest) {
+ this.signRequest = signRequest;
+ }
+
+ public void setValidateResponseSignature(boolean validateResponseSignature) {
+ this.validateResponseSignature = validateResponseSignature;
+ }
+
+ public void setRequestBinding(Binding requestBinding) {
+ this.requestBinding = requestBinding;
+ }
+
+ public void setResponseBinding(Binding responseBinding) {
+ this.responseBinding = responseBinding;
+ }
+
+ public void setRequestBindingUrl(String requestBindingUrl) {
+ this.requestBindingUrl = requestBindingUrl;
+ }
+ }
+
+ public static class DefaultSingleLogoutService implements IDP.SingleLogoutService {
+ private boolean validateRequestSignature;
+ private boolean validateResponseSignature;
+ private boolean signRequest;
+ private boolean signResponse;
+ private Binding requestBinding;
+ private Binding responseBinding;
+ private String requestBindingUrl;
+ private String responseBindingUrl;
+
+ @Override
+ public boolean validateRequestSignature() {
+ return validateRequestSignature;
+ }
+
+ @Override
+ public boolean validateResponseSignature() {
+ return validateResponseSignature;
+ }
+
+ @Override
+ public boolean signRequest() {
+ return signRequest;
+ }
+
+ @Override
+ public boolean signResponse() {
+ return signResponse;
+ }
+
+ @Override
+ public Binding getRequestBinding() {
+ return requestBinding;
+ }
+
+ @Override
+ public Binding getResponseBinding() {
+ return responseBinding;
+ }
+
+ @Override
+ public String getRequestBindingUrl() {
+ return requestBindingUrl;
+ }
+
+ @Override
+ public String getResponseBindingUrl() {
+ return responseBindingUrl;
+ }
+
+ public void setValidateRequestSignature(boolean validateRequestSignature) {
+ this.validateRequestSignature = validateRequestSignature;
+ }
+
+ public void setValidateResponseSignature(boolean validateResponseSignature) {
+ this.validateResponseSignature = validateResponseSignature;
+ }
+
+ public void setSignRequest(boolean signRequest) {
+ this.signRequest = signRequest;
+ }
+
+ public void setSignResponse(boolean signResponse) {
+ this.signResponse = signResponse;
+ }
+
+ public void setRequestBinding(Binding requestBinding) {
+ this.requestBinding = requestBinding;
+ }
+
+ public void setResponseBinding(Binding responseBinding) {
+ this.responseBinding = responseBinding;
+ }
+
+ public void setRequestBindingUrl(String requestBindingUrl) {
+ this.requestBindingUrl = requestBindingUrl;
+ }
+
+ public void setResponseBindingUrl(String responseBindingUrl) {
+ this.responseBindingUrl = responseBindingUrl;
+ }
+ }
+
+ public static class DefaultIDP implements IDP {
+
+ private String entityID;
+ private PublicKey signatureValidationKey;
+ private SingleSignOnService singleSignOnService;
+ private SingleLogoutService singleLogoutService;
+
+ @Override
+ public String getEntityID() {
+ return entityID;
+ }
+
+ @Override
+ public SingleSignOnService getSingleSignOnService() {
+ return singleSignOnService;
+ }
+
+ @Override
+ public SingleLogoutService getSingleLogoutService() {
+ return singleLogoutService;
+ }
+
+ @Override
+ public PublicKey getSignatureValidationKey() {
+ return signatureValidationKey;
+ }
+
+ public void setEntityID(String entityID) {
+ this.entityID = entityID;
+ }
+
+ public void setSignatureValidationKey(PublicKey signatureValidationKey) {
+ this.signatureValidationKey = signatureValidationKey;
+ }
+
+ public void setSingleSignOnService(SingleSignOnService singleSignOnService) {
+ this.singleSignOnService = singleSignOnService;
+ }
+
+ public void setSingleLogoutService(SingleLogoutService singleLogoutService) {
+ this.singleLogoutService = singleLogoutService;
+ }
+ }
+
+ private IDP idp;
+ private boolean configured;
+ private SslRequired sslRequired = SslRequired.EXTERNAL;
+ private String entityID;
+ private String nameIDPolicyFormat;
+ private boolean forceAuthentication;
+ private boolean isPassive;
+ private PrivateKey decryptionKey;
+ private KeyPair signingKeyPair;
+ private String assertionConsumerServiceUrl;
+ private Set<String> roleAttributeNames;
+ private PrincipalNamePolicy principalNamePolicy = PrincipalNamePolicy.FROM_NAME_ID;
+ private String principalAttributeName;
+ private String logoutPage;
+ private SignatureAlgorithm signatureAlgorithm;
+ private String signatureCanonicalizationMethod;
+
+ @Override
+ public IDP getIDP() {
+ return idp;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return configured;
+ }
+
+ @Override
+ public SslRequired getSslRequired() {
+ return sslRequired;
+ }
+
+ @Override
+ public String getEntityID() {
+ return entityID;
+ }
+
+ @Override
+ public String getNameIDPolicyFormat() {
+ return nameIDPolicyFormat;
+ }
+
+ @Override
+ public boolean isForceAuthentication() {
+ return forceAuthentication;
+ }
+
+ @Override
+ public boolean isIsPassive() {
+ return isPassive;
+ }
+
+ @Override
+ public PrivateKey getDecryptionKey() {
+ return decryptionKey;
+ }
+
+ @Override
+ public KeyPair getSigningKeyPair() {
+ return signingKeyPair;
+ }
+
+ @Override
+ public String getAssertionConsumerServiceUrl() {
+ return assertionConsumerServiceUrl;
+ }
+
+ @Override
+ public Set<String> getRoleAttributeNames() {
+ return roleAttributeNames;
+ }
+
+ @Override
+ public PrincipalNamePolicy getPrincipalNamePolicy() {
+ return principalNamePolicy;
+ }
+
+ @Override
+ public String getPrincipalAttributeName() {
+ return principalAttributeName;
+ }
+
+ public void setIdp(IDP idp) {
+ this.idp = idp;
+ }
+
+ public void setConfigured(boolean configured) {
+ this.configured = configured;
+ }
+
+ public void setSslRequired(SslRequired sslRequired) {
+ this.sslRequired = sslRequired;
+ }
+
+ public void setEntityID(String entityID) {
+ this.entityID = entityID;
+ }
+
+ public void setNameIDPolicyFormat(String nameIDPolicyFormat) {
+ this.nameIDPolicyFormat = nameIDPolicyFormat;
+ }
+
+ public void setForceAuthentication(boolean forceAuthentication) {
+ this.forceAuthentication = forceAuthentication;
+ }
+
+ public void setIsPassive(boolean isPassive){
+ this.isPassive = isPassive;
+ }
+
+ public void setDecryptionKey(PrivateKey decryptionKey) {
+ this.decryptionKey = decryptionKey;
+ }
+
+ public void setSigningKeyPair(KeyPair signingKeyPair) {
+ this.signingKeyPair = signingKeyPair;
+ }
+
+ public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
+ this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
+ }
+
+ public void setRoleAttributeNames(Set<String> roleAttributeNames) {
+ this.roleAttributeNames = roleAttributeNames;
+ }
+
+ public void setPrincipalNamePolicy(PrincipalNamePolicy principalNamePolicy) {
+ this.principalNamePolicy = principalNamePolicy;
+ }
+
+ public void setPrincipalAttributeName(String principalAttributeName) {
+ this.principalAttributeName = principalAttributeName;
+ }
+
+ @Override
+ public String getLogoutPage() {
+ return logoutPage;
+ }
+
+ public void setLogoutPage(String logoutPage) {
+ this.logoutPage = logoutPage;
+ }
+
+ @Override
+ public String getSignatureCanonicalizationMethod() {
+ return signatureCanonicalizationMethod;
+ }
+
+ public void setSignatureCanonicalizationMethod(String signatureCanonicalizationMethod) {
+ this.signatureCanonicalizationMethod = signatureCanonicalizationMethod;
+ }
+
+ @Override
+ public SignatureAlgorithm getSignatureAlgorithm() {
+ return signatureAlgorithm;
+ }
+
+ public void setSignatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
+ this.signatureAlgorithm = signatureAlgorithm;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java
new file mode 100644
index 0000000..5fa99a0
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/ecp/EcpAuthenticationHandler.java
@@ -0,0 +1,146 @@
+package org.keycloak.adapters.saml.profile.ecp;
+
+import org.keycloak.adapters.saml.AbstractInitiateLogin;
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.profile.AbstractSamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlInvocationContext;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2AuthnRequestBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.processing.core.saml.v2.util.DocumentUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPHeader;
+import javax.xml.soap.SOAPHeaderElement;
+import javax.xml.soap.SOAPMessage;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class EcpAuthenticationHandler extends AbstractSamlAuthenticationHandler {
+
+ public static final String PAOS_HEADER = "PAOS";
+ public static final String PAOS_CONTENT_TYPE = "application/vnd.paos+xml";
+ private static final String NS_PREFIX_PROFILE_ECP = "ecp";
+ private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
+ private static final String NS_PREFIX_SAML_ASSERTION = "saml";
+ private static final String NS_PREFIX_PAOS_BINDING = "paos";
+
+ public static boolean canHandle(HttpFacade httpFacade) {
+ HttpFacade.Request request = httpFacade.getRequest();
+ String acceptHeader = request.getHeader("Accept");
+ String contentTypeHeader = request.getHeader("Content-Type");
+
+ return (acceptHeader != null && acceptHeader.contains(PAOS_CONTENT_TYPE) && request.getHeader(PAOS_HEADER) != null)
+ || (contentTypeHeader != null && contentTypeHeader.contains(PAOS_CONTENT_TYPE));
+ }
+
+ public static SamlAuthenticationHandler create(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ return new EcpAuthenticationHandler(facade, deployment, sessionStore);
+ }
+
+ private EcpAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ super(facade, deployment, sessionStore);
+ }
+
+ @Override
+ protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+ throw new RuntimeException("Not supported.");
+ }
+
+
+ @Override
+ public AuthOutcome handle(OnSessionCreated onCreateSession) {
+ String header = facade.getRequest().getHeader(PAOS_HEADER);
+
+ if (header != null) {
+ return doHandle(new SamlInvocationContext(), onCreateSession);
+ } else {
+ try {
+ MessageFactory messageFactory = MessageFactory.newInstance();
+ SOAPMessage soapMessage = messageFactory.createMessage(null, facade.getRequest().getInputStream());
+ SOAPBody soapBody = soapMessage.getSOAPBody();
+ Node authnRequestNode = soapBody.getFirstChild();
+ Document document = DocumentUtil.createDocument();
+
+ document.appendChild(document.importNode(authnRequestNode, true));
+
+ String samlResponse = PostBindingUtil.base64Encode(DocumentUtil.asString(document));
+
+ return doHandle(new SamlInvocationContext(null, samlResponse, null), onCreateSession);
+ } catch (Exception e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ }
+ }
+
+ @Override
+ protected AbstractInitiateLogin createChallenge() {
+ return new AbstractInitiateLogin(deployment, sessionStore) {
+ @Override
+ protected void sendAuthnRequest(HttpFacade httpFacade, SAML2AuthnRequestBuilder authnRequestBuilder, BaseSAML2BindingBuilder binding) {
+ try {
+ MessageFactory messageFactory = MessageFactory.newInstance();
+ SOAPMessage message = messageFactory.createMessage();
+
+ SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
+
+ envelope.addNamespaceDeclaration(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get());
+ envelope.addNamespaceDeclaration(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get());
+ envelope.addNamespaceDeclaration(NS_PREFIX_PAOS_BINDING, JBossSAMLURIConstants.PAOS_BINDING.get());
+ envelope.addNamespaceDeclaration(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
+
+ createPaosRequestHeader(envelope);
+ createEcpRequestHeader(envelope);
+
+ SOAPBody body = envelope.getBody();
+
+ body.addDocument(binding.postBinding(authnRequestBuilder.toDocument()).getDocument());
+
+ message.writeTo(httpFacade.getResponse().getOutputStream());
+ } catch (Exception e) {
+ throw new RuntimeException("Could not create AuthnRequest.", e);
+ }
+ }
+
+ private void createEcpRequestHeader(SOAPEnvelope envelope) throws SOAPException {
+ SOAPHeader headers = envelope.getHeader();
+ SOAPHeaderElement ecpRequestHeader = headers.addHeaderElement(envelope.createQName(JBossSAMLConstants.REQUEST.get(), NS_PREFIX_PROFILE_ECP));
+
+ ecpRequestHeader.setMustUnderstand(true);
+ ecpRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+ ecpRequestHeader.addAttribute(envelope.createName("ProviderName"), deployment.getEntityID());
+ ecpRequestHeader.addAttribute(envelope.createName("IsPassive"), "0");
+ ecpRequestHeader.addChildElement(envelope.createQName("Issuer", "saml")).setValue(deployment.getEntityID());
+ ecpRequestHeader.addChildElement(envelope.createQName("IDPList", "samlp"))
+ .addChildElement(envelope.createQName("IDPEntry", "samlp"))
+ .addAttribute(envelope.createName("ProviderID"), deployment.getIDP().getEntityID())
+ .addAttribute(envelope.createName("Name"), deployment.getIDP().getEntityID())
+ .addAttribute(envelope.createName("Loc"), deployment.getIDP().getSingleSignOnService().getRequestBindingUrl());
+ }
+
+ private void createPaosRequestHeader(SOAPEnvelope envelope) throws SOAPException {
+ SOAPHeader headers = envelope.getHeader();
+ SOAPHeaderElement paosRequestHeader = headers.addHeaderElement(envelope.createQName(JBossSAMLConstants.REQUEST.get(), NS_PREFIX_PAOS_BINDING));
+
+ paosRequestHeader.setMustUnderstand(true);
+ paosRequestHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+ paosRequestHeader.addAttribute(envelope.createName("service"), JBossSAMLURIConstants.ECP_PROFILE.get());
+ paosRequestHeader.addAttribute(envelope.createName("responseConsumerURL"), deployment.getAssertionConsumerServiceUrl());
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java
new file mode 100644
index 0000000..4f499c7
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlAuthenticationHandler.java
@@ -0,0 +1,13 @@
+package org.keycloak.adapters.saml.profile;
+
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public interface SamlAuthenticationHandler {
+ AuthOutcome handle(OnSessionCreated onCreateSession);
+ AuthChallenge getChallenge();
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java
new file mode 100644
index 0000000..1155b0d
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/SamlInvocationContext.java
@@ -0,0 +1,37 @@
+package org.keycloak.adapters.saml.profile;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SamlInvocationContext {
+
+ private String samlRequest;
+ private String samlResponse;
+ private String relayState;
+
+ public SamlInvocationContext() {
+ this(null, null, null);
+ }
+
+ public SamlInvocationContext(String samlRequest, String samlResponse, String relayState) {
+ this.samlRequest = samlRequest;
+ this.samlResponse = samlResponse;
+ this.relayState = relayState;
+ }
+
+ public String getSamlRequest() {
+ return this.samlRequest;
+ }
+
+ public String getSamlResponse() {
+ return this.samlResponse;
+ }
+
+ public String getRelayState() {
+ return this.relayState;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
new file mode 100644
index 0000000..f3e98e5
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/webbrowsersso/WebBrowserSsoAuthenticationHandler.java
@@ -0,0 +1,111 @@
+package org.keycloak.adapters.saml.profile.webbrowsersso;
+
+import org.keycloak.adapters.saml.OnSessionCreated;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.SamlUtil;
+import org.keycloak.adapters.saml.profile.AbstractSamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.SamlInvocationContext;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.SAML2LogoutRequestBuilder;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.common.constants.GeneralConstants;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class WebBrowserSsoAuthenticationHandler extends AbstractSamlAuthenticationHandler {
+
+ public static SamlAuthenticationHandler create(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ return new WebBrowserSsoAuthenticationHandler(facade, deployment, sessionStore);
+ }
+
+ private WebBrowserSsoAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ super(facade, deployment, sessionStore);
+ }
+
+ @Override
+ public AuthOutcome handle(OnSessionCreated onCreateSession) {
+ return doHandle(new SamlInvocationContext(facade.getRequest().getFirstParam(GeneralConstants.SAML_REQUEST_KEY),
+ facade.getRequest().getFirstParam(GeneralConstants.SAML_RESPONSE_KEY),
+ facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE)), onCreateSession);
+ }
+
+ @Override
+ protected AuthOutcome handleRequest() {
+ boolean globalLogout = "true".equals(facade.getRequest().getQueryParamValue("GLO"));
+
+ if (globalLogout) {
+ return globalLogout();
+ }
+
+ return AuthOutcome.AUTHENTICATED;
+ }
+
+ @Override
+ protected AuthOutcome logoutRequest(LogoutRequestType request, String relayState) {
+ if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
+ sessionStore.logoutByPrincipal(request.getNameID().getValue());
+ } else {
+ sessionStore.logoutBySsoId(request.getSessionIndex());
+ }
+
+ String issuerURL = deployment.getEntityID();
+ SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+ builder.logoutRequestID(request.getID());
+ builder.destination(deployment.getIDP().getSingleLogoutService().getResponseBindingUrl());
+ builder.issuer(issuerURL);
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder().relayState(relayState);
+ if (deployment.getIDP().getSingleLogoutService().signResponse()) {
+ binding.signatureAlgorithm(deployment.getSignatureAlgorithm())
+ .signWith(deployment.getSigningKeyPair())
+ .signDocument();
+ if (deployment.getSignatureCanonicalizationMethod() != null)
+ binding.canonicalizationMethod(deployment.getSignatureCanonicalizationMethod());
+ }
+
+
+ try {
+ SamlUtil.sendSaml(false, facade, deployment.getIDP().getSingleLogoutService().getResponseBindingUrl(), binding, builder.buildDocument(),
+ deployment.getIDP().getSingleLogoutService().getResponseBinding());
+ } catch (Exception e) {
+ log.error("Could not send logout response SAML request", e);
+ return AuthOutcome.FAILED;
+ }
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+
+ private AuthOutcome globalLogout() {
+ SamlSession account = sessionStore.getAccount();
+ if (account == null) {
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+ SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder()
+ .assertionExpiration(30)
+ .issuer(deployment.getEntityID())
+ .sessionIndex(account.getSessionIndex())
+ .userPrincipal(account.getPrincipal().getSamlSubject(), account.getPrincipal().getNameIDFormat())
+ .destination(deployment.getIDP().getSingleLogoutService().getRequestBindingUrl());
+ BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder();
+ if (deployment.getIDP().getSingleLogoutService().signRequest()) {
+ binding.signWith(deployment.getSigningKeyPair())
+ .signDocument();
+ }
+
+ binding.relayState("logout");
+
+ try {
+ SamlUtil.sendSaml(true, facade, deployment.getIDP().getSingleLogoutService().getRequestBindingUrl(), binding, logoutBuilder.buildDocument(), deployment.getIDP().getSingleLogoutService().getRequestBinding());
+ sessionStore.setCurrentAction(SamlSessionStore.CurrentAction.LOGGING_OUT);
+ } catch (Exception e) {
+ log.error("Could not send global logout SAML request", e);
+ return AuthOutcome.FAILED;
+ }
+ return AuthOutcome.NOT_ATTEMPTED;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java
new file mode 100755
index 0000000..c85fd63
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticationError.java
@@ -0,0 +1,49 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+/**
+ * Object that describes the SAML error that happened.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticationError implements AuthenticationError {
+ public static enum Reason {
+ EXTRACTION_FAILURE,
+ INVALID_SIGNATURE,
+ ERROR_STATUS
+ }
+
+ private Reason reason;
+
+ private StatusResponseType status;
+
+ public SamlAuthenticationError(Reason reason) {
+ this.reason = reason;
+ }
+
+ public SamlAuthenticationError(Reason reason, StatusResponseType status) {
+ this.reason = reason;
+ this.status = status;
+ }
+
+ public SamlAuthenticationError(StatusResponseType statusType) {
+ this.status = statusType;
+ }
+
+ public Reason getReason() {
+ return reason;
+ }
+ public StatusResponseType getStatus() {
+ return status;
+ }
+
+ @Override
+ public String toString() {
+ return "SamlAuthenticationError [reason=" + reason + ", status=" + status + "]";
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
new file mode 100755
index 0000000..cd9affd
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlAuthenticator.java
@@ -0,0 +1,49 @@
+package org.keycloak.adapters.saml;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.ecp.EcpAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.webbrowsersso.WebBrowserSsoAuthenticationHandler;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class SamlAuthenticator {
+
+ protected static Logger log = Logger.getLogger(SamlAuthenticator.class);
+
+ private final SamlAuthenticationHandler handler;
+
+ public SamlAuthenticator(final HttpFacade facade, final SamlDeployment deployment, final SamlSessionStore sessionStore) {
+ this.handler = createAuthenticationHandler(facade, deployment, sessionStore);
+ }
+
+ public AuthChallenge getChallenge() {
+ return this.handler.getChallenge();
+ }
+
+ public AuthOutcome authenticate() {
+ log.debugf("SamlAuthenticator is using handler [%s]", this.handler);
+ return this.handler.handle(new OnSessionCreated() {
+ @Override
+ public void onSessionCreated(SamlSession samlSession) {
+ completeAuthentication(samlSession);
+ }
+ });
+ }
+
+ protected abstract void completeAuthentication(SamlSession samlSession);
+
+ private SamlAuthenticationHandler createAuthenticationHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ if (EcpAuthenticationHandler.canHandle(facade)) {
+ return EcpAuthenticationHandler.create(facade, deployment, sessionStore);
+ }
+
+ // defaults to the web browser sso profile
+ return WebBrowserSsoAuthenticationHandler.create(facade, deployment, sessionStore);
+ }
+}
\ No newline at end of file
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java
new file mode 100755
index 0000000..919bd67
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlConfigResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.HttpFacade.Request;
+
+/**
+ * On multi-tenant scenarios, Keycloak will defer the resolution of a
+ * SamlDeployment to the target application at the request-phase.
+ *
+ * A Request object is passed to the resolver and callers expect a complete
+ * SamlDeployment. Based on this SamlDeployment, Keycloak will resume
+ * authenticating and authorizing the request.
+ *
+ * The easiest way to build a SamlDeployment is to use
+ * DeploymentBuilder , passing the InputStream of an existing
+ * keycloak-saml.xml to the build() method.
+ *
+ * @author Juraci Paixão Kröhling <juraci at kroehling.de>
+ */
+public interface SamlConfigResolver {
+
+ public SamlDeployment resolve(Request facade);
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
new file mode 100755
index 0000000..8c53236
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeployment.java
@@ -0,0 +1,77 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.common.enums.SslRequired;
+import org.keycloak.saml.SignatureAlgorithm;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlDeployment {
+ enum Binding {
+ POST,
+ REDIRECT;
+
+ public static Binding parseBinding(String val) {
+ if (val == null) return POST;
+ return Binding.valueOf(val);
+ }
+ }
+
+ public interface IDP {
+ String getEntityID();
+
+ SingleSignOnService getSingleSignOnService();
+ SingleLogoutService getSingleLogoutService();
+ PublicKey getSignatureValidationKey();
+
+ public interface SingleSignOnService {
+ boolean signRequest();
+ boolean validateResponseSignature();
+ Binding getRequestBinding();
+ Binding getResponseBinding();
+ String getRequestBindingUrl();
+ }
+ public interface SingleLogoutService {
+ boolean validateRequestSignature();
+ boolean validateResponseSignature();
+ boolean signRequest();
+ boolean signResponse();
+ Binding getRequestBinding();
+ Binding getResponseBinding();
+ String getRequestBindingUrl();
+ String getResponseBindingUrl();
+ }
+ }
+
+ public IDP getIDP();
+
+ public boolean isConfigured();
+ SslRequired getSslRequired();
+ String getEntityID();
+ String getNameIDPolicyFormat();
+ boolean isForceAuthentication();
+ boolean isIsPassive();
+ PrivateKey getDecryptionKey();
+ KeyPair getSigningKeyPair();
+ String getSignatureCanonicalizationMethod();
+ SignatureAlgorithm getSignatureAlgorithm();
+ String getAssertionConsumerServiceUrl();
+ String getLogoutPage();
+
+ Set<String> getRoleAttributeNames();
+
+ enum PrincipalNamePolicy {
+ FROM_NAME_ID,
+ FROM_ATTRIBUTE
+ }
+ PrincipalNamePolicy getPrincipalNamePolicy();
+ String getPrincipalAttributeName();
+
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
new file mode 100755
index 0000000..f9c6726
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlDeploymentContext.java
@@ -0,0 +1,19 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlDeploymentContext {
+ private SamlDeployment deployment = null;
+
+ public SamlDeploymentContext(SamlDeployment deployment) {
+ this.deployment = deployment;
+ }
+
+ public SamlDeployment resolveDeployment(HttpFacade facade) {
+ return deployment;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
new file mode 100755
index 0000000..04c1c2f
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
@@ -0,0 +1,141 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.dom.saml.v2.assertion.AssertionType;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlPrincipal implements Serializable, Principal {
+ private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
+ private MultivaluedHashMap<String, String> friendlyAttributes = new MultivaluedHashMap<>();
+ private String name;
+ private String samlSubject;
+ private String nameIDFormat;
+ private AssertionType assertion;
+
+ public SamlPrincipal(AssertionType assertion, String name, String samlSubject, String nameIDFormat, MultivaluedHashMap<String, String> attributes, MultivaluedHashMap<String, String> friendlyAttributes) {
+ this.name = name;
+ this.attributes = attributes;
+ this.friendlyAttributes = friendlyAttributes;
+ this.samlSubject = samlSubject;
+ this.nameIDFormat = nameIDFormat;
+ this.assertion = assertion;
+ }
+
+ public SamlPrincipal() {
+ }
+
+ /**
+ * Get full saml assertion
+ *
+ * @return
+ */
+ public AssertionType getAssertion() {
+ return assertion;
+ }
+
+ /**
+ * Get SAML subject sent in assertion
+ *
+ * @return
+ */
+ public String getSamlSubject() {
+ return samlSubject;
+ }
+
+ /**
+ * Subject nameID format
+ *
+ * @return
+ */
+ public String getNameIDFormat() {
+ return nameIDFormat;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Convenience function that gets Attribute value by attribute name
+ *
+ * @param name
+ * @return
+ */
+ public List<String> getAttributes(String name) {
+ List<String> list = attributes.get(name);
+ if (list != null) {
+ return Collections.unmodifiableList(list);
+ } else {
+ return Collections.emptyList();
+ }
+
+ }
+
+ /**
+ * Convenience function that gets Attribute value by attribute friendly name
+ *
+ * @param friendlyName
+ * @return
+ */
+ public List<String> getFriendlyAttributes(String friendlyName) {
+ List<String> list = friendlyAttributes.get(name);
+ if (list != null) {
+ return Collections.unmodifiableList(list);
+ } else {
+ return Collections.emptyList();
+ }
+
+ }
+
+ /**
+ * Convenience function that gets first value of an attribute by attribute name
+ *
+ * @param name
+ * @return
+ */
+ public String getAttribute(String name) {
+ return attributes.getFirst(name);
+ }
+
+ /**
+ * Convenience function that gets first value of an attribute by attribute name
+ *
+ *
+ * @param friendlyName
+ * @return
+ */
+ public String getFriendlyAttribute(String friendlyName) {
+ return friendlyAttributes.getFirst(friendlyName);
+ }
+
+ /**
+ * Get set of all assertion attribute names
+ *
+ * @return
+ */
+ public Set<String> getAttributeNames() {
+ return Collections.unmodifiableSet(attributes.keySet());
+
+ }
+
+ /**
+ * Get set of all assertion friendly attribute names
+ *
+ * @return
+ */
+ public Set<String> getFriendlyNames() {
+ return Collections.unmodifiableSet(friendlyAttributes.keySet());
+
+ }
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
new file mode 100755
index 0000000..57a4199
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSession.java
@@ -0,0 +1,37 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.KeycloakAccount;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlSession implements Serializable, KeycloakAccount {
+ private SamlPrincipal principal;
+ private Set<String> roles;
+ private String sessionIndex;
+
+ public SamlSession() {
+ }
+
+ public SamlSession(SamlPrincipal principal, Set<String> roles, String sessionIndex) {
+ this.principal = principal;
+ this.roles = roles;
+ this.sessionIndex = sessionIndex;
+ }
+
+ public SamlPrincipal getPrincipal() {
+ return principal;
+ }
+
+ public Set<String> getRoles() {
+ return roles;
+ }
+
+ public String getSessionIndex() {
+ return sessionIndex;
+ }
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
new file mode 100755
index 0000000..1a19464
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlSessionStore.java
@@ -0,0 +1,35 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlSessionStore extends AdapterSessionStore {
+ public static final String CURRENT_ACTION = "SAML_CURRENT_ACTION";
+ public static final String SAML_LOGIN_ERROR_STATUS = "SAML_LOGIN_ERROR_STATUS";
+ public static final String SAML_LOGOUT_ERROR_STATUS = "SAML_LOGOUT_ERROR_STATUS";
+
+ enum CurrentAction {
+ NONE,
+ LOGGING_IN,
+ LOGGING_OUT
+ }
+ void setCurrentAction(CurrentAction action);
+ boolean isLoggingIn();
+ boolean isLoggingOut();
+
+ boolean isLoggedIn();
+ SamlSession getAccount();
+ void saveAccount(SamlSession account);
+ String getRedirectUri();
+ void logoutAccount();
+ void logoutByPrincipal(String principal);
+ void logoutBySsoId(List<String> ssoIds);
+
+}
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
new file mode 100755
index 0000000..d3d9a0f
--- /dev/null
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/SamlUtil.java
@@ -0,0 +1,35 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlUtil {
+ public static void sendSaml(boolean asRequest, HttpFacade httpFacade, String actionUrl,
+ BaseSAML2BindingBuilder binding, Document document,
+ SamlDeployment.Binding samlBinding) throws ProcessingException, ConfigurationException, IOException {
+ if (samlBinding == SamlDeployment.Binding.POST) {
+ String html = asRequest ? binding.postBinding(document).getHtmlRequest(actionUrl) : binding.postBinding(document).getHtmlResponse(actionUrl) ;
+ httpFacade.getResponse().setStatus(200);
+ httpFacade.getResponse().setHeader("Content-Type", "text/html");
+ httpFacade.getResponse().setHeader("Pragma", "no-cache");
+ httpFacade.getResponse().setHeader("Cache-Control", "no-cache, no-store");
+ httpFacade.getResponse().getOutputStream().write(html.getBytes());
+ httpFacade.getResponse().end();
+ } else {
+ String uri = asRequest ? binding.redirectBinding(document).requestURI(actionUrl).toString() : binding.redirectBinding(document).responseURI(actionUrl).toString();
+ httpFacade.getResponse().setStatus(302);
+ httpFacade.getResponse().setHeader("Location", uri);
+ httpFacade.getResponse().end();
+ }
+ }
+
+}
diff --git a/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd
new file mode 100755
index 0000000..d3e55f9
--- /dev/null
+++ b/adapters/saml/core/src/main/resources/schema/keycloak_saml_adapter_1_6.xsd
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema version="1.0"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns="urn:keycloak:saml:adapter"
+ targetNamespace="urn:keycloak:saml:adapter"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified">
+
+ <xs:element name="keycloak-saml-adapter" type="adapter-type"/>
+ <xs:complexType name="adapter-type">
+ <xs:annotation>
+ <xs:documentation>
+ <![CDATA[
+ The Keycloak SAML Adapter keycloak-saml.xml config file
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:all>
+ <xs:element name="SP" maxOccurs="1" minOccurs="0" type="sp-type"/>
+ </xs:all>
+ </xs:complexType>
+
+ <xs:complexType name="sp-type">
+ <xs:all>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="PrincipalNameMapping" type="principal-name-mapping-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="RoleIdentifiers" type="role-identifiers-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="IDP" type="idp-type" minOccurs="1" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="entityID" type="xs:string" use="required"/>
+ <xs:attribute name="sslPolicy" type="xs:string" use="optional"/>
+ <xs:attribute name="nameIDPolicyFormat" type="xs:string" use="optional"/>
+ <xs:attribute name="logoutPage" type="xs:string" use="optional"/>
+ <xs:attribute name="forceAuthentication" type="xs:boolean" use="optional"/>
+ <xs:attribute name="isPassive" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="keys-type">
+ <xs:sequence>
+ <xs:element name="Key" type="key-type" minOccurs="1" maxOccurs="2"/>
+ </xs:sequence>
+ </xs:complexType>
+ <xs:complexType name="key-type">
+ <xs:all>
+ <xs:element name="KeyStore" maxOccurs="1" minOccurs="0" type="key-store-type"/>
+ <xs:element name="PrivateKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="PublicKeyPem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="CertificatePem" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="signing" type="xs:boolean" use="optional"/>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="key-store-type">
+ <xs:all>
+ <xs:element name="PrivateKey" maxOccurs="1" minOccurs="0" type="private-key-type"/>
+ <xs:element name="Certificate" type="certificate-type" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="file" type="xs:string" use="optional"/>
+ <xs:attribute name="resource" type="xs:string" use="optional"/>
+ <xs:attribute name="password" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="private-key-type">
+ <xs:attribute name="alias" type="xs:string" use="required"/>
+ <xs:attribute name="password" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="certificate-type">
+ <xs:attribute name="alias" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="principal-name-mapping-type">
+ <xs:attribute name="policy" type="xs:string" use="required"/>
+ <xs:attribute name="attribute" type="xs:string" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="role-identifiers-type">
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="Attribute" maxOccurs="unbounded" minOccurs="0" type="attribute-type"/>
+ </xs:choice>
+ </xs:complexType>
+ <xs:complexType name="attribute-type">
+ <xs:attribute name="name" type="xs:string" use="required"/>
+ </xs:complexType>
+ <xs:complexType name="idp-type">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="SingleSignOnService" maxOccurs="1" minOccurs="1" type="sign-on-type"/>
+ <xs:element name="SingleLogoutService" type="logout-type" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="Keys" type="keys-type" minOccurs="0" maxOccurs="1"/>
+ </xs:sequence>
+ <xs:attribute name="entityID" type="xs:string" use="required"/>
+ <xs:attribute name="signaturesRequired" type="xs:boolean" use="required"/>
+ <xs:attribute name="signatureAlgorithm" type="xs:string" use="optional"/>
+ <xs:attribute name="signatureCanonicalizationMethod" type="xs:string" use="optional"/>
+ <xs:attribute name="encryption" type="xs:boolean" use="optional"/>
+ </xs:complexType>
+ <xs:complexType name="sign-on-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="bindingUrl" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+ <xs:complexType name="logout-type">
+ <xs:attribute name="signRequest" type="xs:boolean" use="optional"/>
+ <xs:attribute name="signResponse" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateRequestSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="validateResponseSignature" type="xs:boolean" use="optional"/>
+ <xs:attribute name="requestBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="responseBinding" type="xs:string" use="optional"/>
+ <xs:attribute name="postBindingUrl" type="xs:string" use="optional"/>
+ <xs:attribute name="redirectBindingUrl" type="xs:string" use="optional"/>
+ </xs:complexType>
+
+
+
+
+</xs:schema>
diff --git a/adapters/saml/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java b/adapters/saml/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
new file mode 100755
index 0000000..5a6e037
--- /dev/null
+++ b/adapters/saml/core/src/test/java/org/keycloak/test/adapters/saml/XmlParserTest.java
@@ -0,0 +1,118 @@
+package org.keycloak.test.adapters.saml;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.adapters.saml.config.IDP;
+import org.keycloak.adapters.saml.config.Key;
+import org.keycloak.adapters.saml.config.KeycloakSamlAdapter;
+import org.keycloak.adapters.saml.config.SP;
+import org.keycloak.adapters.saml.config.parsers.KeycloakSamlAdapterXMLParser;
+import org.keycloak.saml.common.util.StaxParserUtil;
+
+import javax.xml.XMLConstants;
+import javax.xml.stream.XMLEventReader;
+import javax.xml.transform.stax.StAXSource;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import javax.xml.validation.Validator;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class XmlParserTest {
+
+ @Test
+ public void testValidation() throws Exception {
+ {
+ InputStream schema = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
+ InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
+ Assert.assertNotNull(is);
+ Assert.assertNotNull(schema);
+ StaxParserUtil.validate(is, schema);
+ }
+ {
+ InputStream sch = KeycloakSamlAdapterXMLParser.class.getResourceAsStream("/schema/keycloak_saml_adapter_1_6.xsd");
+ InputStream doc = getClass().getResourceAsStream("/keycloak-saml2.xml");
+ Assert.assertNotNull(doc);
+ Assert.assertNotNull(sch);
+ try {
+ SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ Schema schema = factory.newSchema(new StreamSource(sch));
+ Validator validator = schema.newValidator();
+ StreamSource source = new StreamSource(doc);
+ source.setSystemId("/keycloak-saml2.xml");
+ validator.validate(source);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+
+ }
+
+
+ }
+
+ @Test
+ public void testXmlParser() throws Exception {
+ InputStream is = getClass().getResourceAsStream("/keycloak-saml.xml");
+ Assert.assertNotNull(is);
+ KeycloakSamlAdapterXMLParser parser = new KeycloakSamlAdapterXMLParser();
+ KeycloakSamlAdapter config = (KeycloakSamlAdapter)parser.parse(is);
+ Assert.assertNotNull(config);
+ Assert.assertEquals(1, config.getSps().size());
+ SP sp = config.getSps().get(0);
+ Assert.assertEquals("sp", sp.getEntityID());
+ Assert.assertEquals("ssl", sp.getSslPolicy());
+ Assert.assertEquals("format", sp.getNameIDPolicyFormat());
+ Assert.assertTrue(sp.isForceAuthentication());
+ Assert.assertTrue(sp.isIsPassive());
+ Assert.assertEquals(2, sp.getKeys().size());
+ Key signing = sp.getKeys().get(0);
+ Assert.assertTrue(signing.isSigning());
+ Key.KeyStoreConfig keystore = signing.getKeystore();
+ Assert.assertNotNull(keystore);
+ Assert.assertEquals("file", keystore.getFile());
+ Assert.assertEquals("cp", keystore.getResource());
+ Assert.assertEquals("pw", keystore.getPassword());
+ Assert.assertEquals("private alias", keystore.getPrivateKeyAlias());
+ Assert.assertEquals("private pw", keystore.getPrivateKeyPassword());
+ Assert.assertEquals("cert alias", keystore.getCertificateAlias());
+ Key encryption = sp.getKeys().get(1);
+ Assert.assertTrue(encryption.isEncryption());
+ Assert.assertEquals("private pem", encryption.getPrivateKeyPem());
+ Assert.assertEquals("public pem", encryption.getPublicKeyPem());
+ Assert.assertEquals("policy", sp.getPrincipalNameMapping().getPolicy());
+ Assert.assertEquals("attribute", sp.getPrincipalNameMapping().getAttributeName());
+ Assert.assertTrue(sp.getRoleAttributes().size() == 1);
+ Assert.assertTrue(sp.getRoleAttributes().contains("member"));
+
+ IDP idp = sp.getIdp();
+ Assert.assertEquals("idp", idp.getEntityID());
+ Assert.assertEquals("RSA", idp.getSignatureAlgorithm());
+ Assert.assertEquals("canon", idp.getSignatureCanonicalizationMethod());
+ Assert.assertTrue(idp.getSingleSignOnService().isSignRequest());
+ Assert.assertTrue(idp.getSingleSignOnService().isValidateResponseSignature());
+ Assert.assertEquals("post", idp.getSingleSignOnService().getRequestBinding());
+ Assert.assertEquals("url", idp.getSingleSignOnService().getBindingUrl());
+
+ Assert.assertTrue(idp.getSingleLogoutService().isSignRequest());
+ Assert.assertTrue(idp.getSingleLogoutService().isSignResponse());
+ Assert.assertTrue(idp.getSingleLogoutService().isValidateRequestSignature());
+ Assert.assertTrue(idp.getSingleLogoutService().isValidateResponseSignature());
+ Assert.assertEquals("redirect", idp.getSingleLogoutService().getRequestBinding());
+ Assert.assertEquals("post", idp.getSingleLogoutService().getResponseBinding());
+ Assert.assertEquals("posturl", idp.getSingleLogoutService().getPostBindingUrl());
+ Assert.assertEquals("redirecturl", idp.getSingleLogoutService().getRedirectBindingUrl());
+
+ Assert.assertTrue(idp.getKeys().size() == 1);
+ Assert.assertTrue(idp.getKeys().get(0).isSigning());
+ Assert.assertEquals("cert pem", idp.getKeys().get(0).getCertificatePem());
+
+
+
+
+ }
+}
diff --git a/adapters/saml/core/src/test/resources/keycloak-saml.xml b/adapters/saml/core/src/test/resources/keycloak-saml.xml
new file mode 100755
index 0000000..ae8c96d
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/keycloak-saml.xml
@@ -0,0 +1,57 @@
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
+ <SP entityID="sp"
+ sslPolicy="ssl"
+ nameIDPolicyFormat="format"
+ forceAuthentication="true"
+ isPassive="true">
+ <Keys>
+ <Key signing="true" >
+ <KeyStore file="file" resource="cp" password="pw">
+ <PrivateKey alias="private alias" password="private pw"/>
+ <Certificate alias="cert alias"/>
+ </KeyStore>
+ </Key>
+ <Key encryption="true">
+ <PrivateKeyPem>
+ private pem
+ </PrivateKeyPem>
+ <PublicKeyPem>
+ public pem
+ </PublicKeyPem>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="policy" attribute="attribute"/>
+ <RoleIdentifiers>
+ <Attribute name="member"/>
+ </RoleIdentifiers>
+ <IDP entityID="idp"
+ signatureAlgorithm="RSA"
+ signatureCanonicalizationMethod="canon"
+ signaturesRequired="true"
+ >
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="post"
+ bindingUrl="url"
+ />
+
+ <SingleLogoutService
+ validateRequestSignature="true"
+ validateResponseSignature="true"
+ signRequest="true"
+ signResponse="true"
+ requestBinding="redirect"
+ responseBinding="post"
+ postBindingUrl="posturl"
+ redirectBindingUrl="redirecturl"
+ />
+ <Keys>
+ <Key signing="true">
+ <CertificatePem>
+ cert pem
+ </CertificatePem>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
diff --git a/adapters/saml/core/src/test/resources/keycloak-saml2.xml b/adapters/saml/core/src/test/resources/keycloak-saml2.xml
new file mode 100755
index 0000000..7662e61
--- /dev/null
+++ b/adapters/saml/core/src/test/resources/keycloak-saml2.xml
@@ -0,0 +1,47 @@
+<keycloak-saml-adapter xmlns="urn:keycloak:saml:adapter">
+ <SP entityID="sp"
+ sslPolicy="ssl"
+ nameIDPolicyFormat="format"
+ signatureAlgorithm=""
+ signatureCanonicalizationMethod=""
+ forceAuthentication="true"
+ isPassive="true">
+ <Keys>
+ <Key signing="true" >
+ <KeyStore file="file" resource="cp" password="pw">
+ <PrivateKey alias="private alias" password="private pw"/>
+ <Certificate alias="cert alias"/>
+ </KeyStore>
+ </Key>
+ <Key encryption="true">
+ <PrivateKeyPemmm>
+ private pem
+ </PrivateKeyPemmm>
+ <PublicKeyPem>
+ public pem
+ </PublicKeyPem>
+ </Key>
+ </Keys>
+ <PrincipalNameMapping policy="policy" attribute="attribute"/>
+ <RoleMapping>
+ <Attribute name="member"/>
+ </RoleMapping>
+ <IDP entityID="idp"
+ signaturesRequired="true"
+ >
+ <SingleSignOnService signRequest="true"
+ validateResponseSignature="true"
+ requestBinding="post"
+ bindingUrl="url"
+ />
+
+ <Keys>
+ <Key signing="true">
+ <CertificatePem>
+ cert pem
+ </CertificatePem>
+ </Key>
+ </Keys>
+ </IDP>
+ </SP>
+</keycloak-saml-adapter>
\ No newline at end of file
adapters/saml/jetty/jetty8.1/pom.xml 126(+126 -0)
diff --git a/adapters/saml/jetty/jetty8.1/pom.xml b/adapters/saml/jetty/jetty8.1/pom.xml
new file mode 100755
index 0000000..a945230
--- /dev/null
+++ b/adapters/saml/jetty/jetty8.1/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-jetty81-adapter</artifactId>
+ <name>Keycloak Jetty 8.1.x SAML Integration</name>
+ <properties>
+ <jetty9.version>8.1.17.v20150415</jetty9.version>
+ <keycloak.osgi.export>
+ org.keycloak.adapters.jetty.*
+ </keycloak.osgi.export>
+ <keycloak.osgi.import>
+ javax.servlet.*;version="[2.5,4)";resolution:=optional,
+ org.keycloak.*;version="${project.version}",
+ *;resolution:=optional
+ </keycloak.osgi.import>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-jetty-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+ <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Bundle-ClassPath>.</Bundle-ClassPath>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>${keycloak.osgi.import}</Import-Package>
+ <Export-Package>${keycloak.osgi.export}</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java b/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
new file mode 100755
index 0000000..7ff2269
--- /dev/null
+++ b/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
@@ -0,0 +1,94 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.MultiMap;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JettyAdapterSessionStore implements AdapterSessionStore {
+ public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
+ protected Request myRequest;
+
+ public JettyAdapterSessionStore(Request request) {
+ this.myRequest = request; // for IDE/compilation purposes
+ }
+
+ protected MultiMap<String> extractFormParameters(Request base_request) {
+ MultiMap<String> formParameters = new MultiMap<String>();
+ base_request.extractParameters();
+ return base_request.getParameters();
+ }
+ protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
+ base_request.setParameters(j_post);
+ }
+
+ public boolean restoreRequest() {
+ HttpSession session = myRequest.getSession(false);
+ if (session == null) return false;
+ synchronized (session) {
+ String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
+ if (j_uri != null) {
+ // check if the request is for the same url as the original and restore
+ // params if it was a post
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ if (j_uri.equals(buf.toString())) {
+ String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
+ myRequest.setMethod(method);
+ MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
+ if (j_post != null) {
+ myRequest.setContentType("application/x-www-form-urlencoded");
+ MultiMap<String> map = new MultiMap<String>();
+ for (String key : j_post.keySet()) {
+ for (String val : j_post.getList(key)) {
+ map.add(key, val);
+ }
+ }
+ restoreFormParameters(map, myRequest);
+ }
+ session.removeAttribute(FormAuthenticator.__J_URI);
+ session.removeAttribute(JettyHttpFacade.__J_METHOD);
+ session.removeAttribute(FormAuthenticator.__J_POST);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void saveRequest() {
+ // remember the current URI
+ HttpSession session = myRequest.getSession();
+ synchronized (session) {
+ // But only if it is not set already, or we save every uri that leads to a login form redirect
+ if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
+ session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
+
+ if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
+ MultiMap<String> formParameters = extractFormParameters(myRequest);
+ MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
+ for (String key : formParameters.keySet()) {
+ for (Object value : formParameters.getValues(key)) {
+ map.add(key, (String) value);
+ }
+ }
+ session.setAttribute(CACHED_FORM_PARAMETERS, map);
+ }
+ }
+ }
+ }
+
+}
diff --git a/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java b/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
new file mode 100755
index 0000000..9bd669d
--- /dev/null
+++ b/adapters/saml/jetty/jetty8.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
@@ -0,0 +1,44 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.saml.SamlDeployment;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAuthenticator extends AbstractSamlAuthenticator {
+
+ public KeycloakSamlAuthenticator() {
+ super();
+ }
+
+
+ @Override
+ public AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment) {
+ return new JettyAdapterSessionStore(request);
+ }
+
+ @Override
+ protected Request resolveRequest(ServletRequest req) {
+ return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest();
+ }
+
+ @Override
+ public Authentication createAuthentication(UserIdentity userIdentity) {
+ return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
+ @Override
+ public void logout() {
+ logoutCurrent(AbstractHttpConnection.getCurrentConnection().getRequest());
+ }
+ };
+ }
+
+
+}
adapters/saml/jetty/jetty9.1/pom.xml 141(+141 -0)
diff --git a/adapters/saml/jetty/jetty9.1/pom.xml b/adapters/saml/jetty/jetty9.1/pom.xml
new file mode 100755
index 0000000..b5a2137
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.1/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-jetty91-adapter</artifactId>
+ <name>Keycloak Jetty 9.1.x SAML Integration</name>
+ <properties>
+ <jetty9.version>9.1.5.v20140505</jetty9.version>
+ <keycloak.osgi.export>
+ org.keycloak.adapters.jetty.*
+ </keycloak.osgi.export>
+ <keycloak.osgi.import>
+ org.eclipse.jetty.*;version="[9.1,9.2)";resolution:=optional,
+ javax.servlet.*;version="[3.0,4)";resolution:=optional,
+ org.keycloak.*;version="${project.version}",
+ *;resolution:=optional
+ </keycloak.osgi.import>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-jetty-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+ <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Bundle-ClassPath>.</Bundle-ClassPath>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>${keycloak.osgi.import}</Import-Package>
+ <Export-Package>${keycloak.osgi.export}</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java b/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
new file mode 100755
index 0000000..8495a5a
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
@@ -0,0 +1,94 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.MultiMap;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JettyAdapterSessionStore implements AdapterSessionStore {
+ public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
+ protected Request myRequest;
+
+ public JettyAdapterSessionStore(Request request) {
+ this.myRequest = request; // for IDE/compilation purposes
+ }
+
+ protected MultiMap<String> extractFormParameters(Request base_request) {
+ base_request.extractParameters();
+ return base_request.getParameters();
+ }
+ protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
+ base_request.setParameters(j_post);
+ }
+
+ public boolean restoreRequest() {
+ HttpSession session = myRequest.getSession(false);
+ if (session == null) return false;
+ synchronized (session) {
+ String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
+ if (j_uri != null) {
+ // check if the request is for the same url as the original and restore
+ // params if it was a post
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ if (j_uri.equals(buf.toString())) {
+ String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
+ myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method);
+ MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
+ if (j_post != null) {
+ myRequest.setContentType("application/x-www-form-urlencoded");
+ MultiMap<String> map = new MultiMap<String>();
+ for (String key : j_post.keySet()) {
+ for (String val : j_post.getList(key)) {
+ map.add(key, val);
+ }
+ }
+ restoreFormParameters(map, myRequest);
+ }
+ session.removeAttribute(FormAuthenticator.__J_URI);
+ session.removeAttribute(JettyHttpFacade.__J_METHOD);
+ session.removeAttribute(FormAuthenticator.__J_POST);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void saveRequest() {
+ // remember the current URI
+ HttpSession session = myRequest.getSession();
+ synchronized (session) {
+ // But only if it is not set already, or we save every uri that leads to a login form redirect
+ if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
+ session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
+
+ if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
+ MultiMap<String> formParameters = extractFormParameters(myRequest);
+ MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
+ for (String key : formParameters.keySet()) {
+ for (Object value : formParameters.getValues(key)) {
+ map.add(key, (String) value);
+ }
+ }
+ session.setAttribute(CACHED_FORM_PARAMETERS, map);
+ }
+ }
+ }
+ }
+
+}
diff --git a/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java b/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
new file mode 100755
index 0000000..f5fdf7d
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.1/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
@@ -0,0 +1,44 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.saml.SamlDeployment;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAuthenticator extends AbstractSamlAuthenticator {
+
+ public KeycloakSamlAuthenticator() {
+ super();
+ }
+
+
+ @Override
+ public AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment) {
+ return new JettyAdapterSessionStore(request);
+ }
+
+ @Override
+ protected Request resolveRequest(ServletRequest req) {
+ return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
+ }
+
+ @Override
+ public Authentication createAuthentication(UserIdentity userIdentity) {
+ return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
+ @Override
+ public void logout() {
+ logoutCurrent(HttpChannel.getCurrentHttpChannel().getRequest());
+ }
+ };
+ }
+
+
+}
adapters/saml/jetty/jetty9.2/pom.xml 141(+141 -0)
diff --git a/adapters/saml/jetty/jetty9.2/pom.xml b/adapters/saml/jetty/jetty9.2/pom.xml
new file mode 100755
index 0000000..1aabf30
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.2/pom.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-jetty92-adapter</artifactId>
+ <name>Keycloak Jetty 9.2.x SAML Integration</name>
+ <properties>
+ <jetty9.version>9.2.4.v20141103</jetty9.version>
+ <keycloak.osgi.export>
+ org.keycloak.adapters.jetty.*
+ </keycloak.osgi.export>
+ <keycloak.osgi.import>
+ org.eclipse.jetty.*;resolution:=optional,
+ javax.servlet.*;version="[3.0,4)";resolution:=optional,
+ org.keycloak.*;version="${project.version}",
+ *;resolution:=optional
+ </keycloak.osgi.import>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-jetty-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+ <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Bundle-ClassPath>.</Bundle-ClassPath>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>${keycloak.osgi.import}</Import-Package>
+ <Export-Package>${keycloak.osgi.export}</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java b/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
new file mode 100755
index 0000000..62c9a70
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/JettyAdapterSessionStore.java
@@ -0,0 +1,95 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.util.MultiMap;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
+import org.keycloak.common.util.MultivaluedHashMap;
+
+import javax.servlet.http.HttpSession;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JettyAdapterSessionStore implements AdapterSessionStore {
+ public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
+ protected Request myRequest;
+
+ public JettyAdapterSessionStore(Request request) {
+ this.myRequest = request; // for IDE/compilation purposes
+ }
+
+ protected MultiMap<String> extractFormParameters(Request base_request) {
+ MultiMap<String> formParameters = new MultiMap<String>();
+ base_request.extractFormParameters(formParameters);
+ return formParameters;
+ }
+ protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
+ base_request.setContentParameters(j_post);
+ }
+
+ public boolean restoreRequest() {
+ HttpSession session = myRequest.getSession(false);
+ if (session == null) return false;
+ synchronized (session) {
+ String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
+ if (j_uri != null) {
+ // check if the request is for the same url as the original and restore
+ // params if it was a post
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ if (j_uri.equals(buf.toString())) {
+ String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
+ myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method);
+ MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
+ if (j_post != null) {
+ myRequest.setContentType("application/x-www-form-urlencoded");
+ MultiMap<String> map = new MultiMap<String>();
+ for (String key : j_post.keySet()) {
+ for (String val : j_post.getList(key)) {
+ map.add(key, val);
+ }
+ }
+ restoreFormParameters(map, myRequest);
+ }
+ session.removeAttribute(FormAuthenticator.__J_URI);
+ session.removeAttribute(JettyHttpFacade.__J_METHOD);
+ session.removeAttribute(FormAuthenticator.__J_POST);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void saveRequest() {
+ // remember the current URI
+ HttpSession session = myRequest.getSession();
+ synchronized (session) {
+ // But only if it is not set already, or we save every uri that leads to a login form redirect
+ if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
+ StringBuffer buf = myRequest.getRequestURL();
+ if (myRequest.getQueryString() != null)
+ buf.append("?").append(myRequest.getQueryString());
+ session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
+ session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
+
+ if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
+ MultiMap<String> formParameters = extractFormParameters(myRequest);
+ MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
+ for (String key : formParameters.keySet()) {
+ for (Object value : formParameters.getValues(key)) {
+ map.add(key, (String) value);
+ }
+ }
+ session.setAttribute(CACHED_FORM_PARAMETERS, map);
+ }
+ }
+ }
+ }
+
+}
diff --git a/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java b/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
new file mode 100755
index 0000000..03ea508
--- /dev/null
+++ b/adapters/saml/jetty/jetty9.2/src/main/java/org/keycloak/adapters/saml/jetty/KeycloakSamlAuthenticator.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.HttpChannel;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.UserIdentity;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.saml.SamlDeployment;
+
+import javax.servlet.ServletRequest;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlAuthenticator extends AbstractSamlAuthenticator {
+
+ public KeycloakSamlAuthenticator() {
+ super();
+ }
+
+
+ @Override
+ protected Request resolveRequest(ServletRequest req) {
+ return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
+ }
+
+ @Override
+ public Authentication createAuthentication(UserIdentity userIdentity) {
+ return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
+ @Override
+ public void logout() {
+ logoutCurrent(HttpChannel.getCurrentHttpChannel().getRequest());
+ }
+ };
+ }
+
+ @Override
+ public AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment) {
+ return new JettyAdapterSessionStore(request);
+ }
+}
adapters/saml/jetty/jetty-core/pom.xml 131(+131 -0)
diff --git a/adapters/saml/jetty/jetty-core/pom.xml b/adapters/saml/jetty/jetty-core/pom.xml
new file mode 100755
index 0000000..c339b04
--- /dev/null
+++ b/adapters/saml/jetty/jetty-core/pom.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-jetty-adapter-core</artifactId>
+ <name>Keycloak Jetty Core SAML Integration</name>
+ <properties>
+ <jetty9.version>8.1.17.v20150415</jetty9.version>
+ <keycloak.osgi.export>
+ org.keycloak.adapters.jetty.core.*
+ </keycloak.osgi.export>
+ <keycloak.osgi.import>
+ org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
+ javax.servlet.*;version="[2.5,4)";resolution:=optional,
+ org.keycloak.*;version="${project.version}",
+ *;resolution:=optional
+ </keycloak.osgi.import>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jetty-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-server</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-util</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-security</artifactId>
+ <version>${jetty9.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+
+ <!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <instructions>
+ <Bundle-ClassPath>.</Bundle-ClassPath>
+ <Bundle-Name>${project.name}</Bundle-Name>
+ <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+ <Import-Package>${keycloak.osgi.import}</Import-Package>
+ <Export-Package>${keycloak.osgi.export}</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
new file mode 100755
index 0000000..2df0fad
--- /dev/null
+++ b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/AbstractSamlAuthenticator.java
@@ -0,0 +1,304 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.security.DefaultUserIdentity;
+import org.eclipse.jetty.security.ServerAuthException;
+import org.eclipse.jetty.security.UserAuthentication;
+import org.eclipse.jetty.security.authentication.DeferredAuthentication;
+import org.eclipse.jetty.security.authentication.FormAuthenticator;
+import org.eclipse.jetty.security.authentication.LoginAuthenticator;
+import org.eclipse.jetty.server.Authentication;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.UserIdentity;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.URIUtil;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
+import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
+import org.keycloak.adapters.saml.AdapterConstants;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlConfigResolver;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+import javax.security.auth.Subject;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractSamlAuthenticator extends LoginAuthenticator {
+ public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+ protected static final Logger log = Logger.getLogger(AbstractSamlAuthenticator.class);
+ protected SamlDeploymentContext deploymentContext;
+ protected SamlConfigResolver configResolver;
+ protected String errorPage;
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+
+ public AbstractSamlAuthenticator() {
+ super();
+ }
+
+ private static InputStream getJSONFromServletContext(ServletContext servletContext) {
+ String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
+ if (json == null) {
+ return null;
+ }
+ return new ByteArrayInputStream(json.getBytes());
+ }
+
+ public JettySamlSessionStore getTokenStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
+ JettySamlSessionStore store = (JettySamlSessionStore) request.getAttribute(TOKEN_STORE_NOTE);
+ if (store != null) {
+ return store;
+ }
+ store = new JettySamlSessionStore(request, createSessionTokenStore(request, resolvedDeployment), facade, idMapper, new JettyUserSessionManagement(request.getSessionManager()));
+
+ request.setAttribute(TOKEN_STORE_NOTE, store);
+ return store;
+ }
+
+ public abstract AdapterSessionStore createSessionTokenStore(Request request, SamlDeployment resolvedDeployment);
+
+ public void logoutCurrent(Request request) {
+ JettyHttpFacade facade = new JettyHttpFacade(request, null);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ JettySamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.logoutAccount();
+ }
+
+ protected void forwardToLogoutPage(Request request, HttpServletResponse response, SamlDeployment deployment) {
+ RequestDispatcher disp = request.getRequestDispatcher(deployment.getLogoutPage());
+ //make sure the login page is never cached
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+
+ try {
+ disp.forward(request, response);
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+
+
+ @Override
+ public void setConfiguration(AuthConfiguration configuration) {
+ //super.setConfiguration(configuration);
+ initializeKeycloak();
+ String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
+ setErrorPage(error);
+ }
+
+ private void setErrorPage(String path) {
+ if (path == null || path.trim().length() == 0) {
+ } else {
+ if (!path.startsWith("/")) {
+ path = "/" + path;
+ }
+ errorPage = path;
+
+ if (errorPage.indexOf('?') > 0)
+ errorPage = errorPage.substring(0, errorPage.indexOf('?'));
+ }
+ }
+
+ @Override
+ public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException {
+ return true;
+ }
+
+
+
+ public SamlConfigResolver getConfigResolver() {
+ return configResolver;
+ }
+
+ public void setConfigResolver(SamlConfigResolver configResolver) {
+ this.configResolver = configResolver;
+ }
+
+ @SuppressWarnings("UseSpecificCatch")
+ public void initializeKeycloak() {
+
+ ServletContext theServletContext = null;
+ ContextHandler.Context currentContext = ContextHandler.getCurrentContext();
+ if (currentContext != null) {
+ String contextPath = currentContext.getContextPath();
+
+ if ("".equals(contextPath)) {
+ // This could be the case in osgi environment when deploying apps through pax whiteboard extension.
+ theServletContext = currentContext;
+ } else {
+ theServletContext = currentContext.getContext(contextPath);
+ }
+ }
+
+ // Jetty 9.1.x servlet context will be null :(
+ if (configResolver == null && theServletContext != null) {
+ String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver");
+ if (configResolverClass != null) {
+ try {
+ configResolver = (SamlConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance();
+ log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+ } catch (Exception ex) {
+ log.infov("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
+ }
+ }
+ }
+
+ if (configResolver != null) {
+ //deploymentContext = new AdapterDeploymentContext(configResolver);
+ } else if (theServletContext != null) {
+ InputStream configInputStream = getConfigInputStream(theServletContext);
+ if (configInputStream != null) {
+ final ServletContext servletContext = theServletContext;
+ SamlDeployment deployment = null;
+ try {
+ deployment = new DeploymentBuilder().build(configInputStream, new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ return servletContext.getResourceAsStream(resource);
+ }
+ });
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ deploymentContext = new SamlDeploymentContext(deployment);
+ }
+ }
+ if (theServletContext != null)
+ theServletContext.setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+ }
+
+ private InputStream getConfigInputStream(ServletContext servletContext) {
+ InputStream is = getJSONFromServletContext(servletContext);
+ if (is == null) {
+ String path = servletContext.getInitParameter("keycloak.config.file");
+ if (path == null) {
+ is = servletContext.getResourceAsStream("/WEB-INF/keycloak-saml.xml");
+ } else {
+ try {
+ is = new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return is;
+ }
+
+ @Override
+ public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException {
+ if (log.isTraceEnabled()) {
+ log.trace("*** authenticate");
+ }
+ Request request = resolveRequest(req);
+ JettyHttpFacade facade = new JettyHttpFacade(request, (HttpServletResponse) res);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment == null || !deployment.isConfigured()) {
+ log.debug("*** deployment isn't configured return false");
+ return Authentication.UNAUTHENTICATED;
+ }
+ if (!mandatory)
+ return new DeferredAuthentication(this);
+ JettySamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
+
+ SamlAuthenticator authenticator = new SamlAuthenticator(facade, deployment, tokenStore ) {
+ @Override
+ protected void completeAuthentication(SamlSession account) {
+
+ }
+ };
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ if (facade.isEnded()) {
+ return Authentication.SEND_SUCCESS;
+ }
+ SamlSession samlSession = tokenStore.getAccount();
+ Authentication authentication = register(request, samlSession);
+ return authentication;
+
+ }
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ logoutCurrent(request);
+ if (deployment.getLogoutPage() != null) {
+ forwardToLogoutPage(request, (HttpServletResponse)res, deployment);
+
+ }
+ return Authentication.SEND_CONTINUE;
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ challenge.challenge(facade);
+ }
+ return Authentication.SEND_CONTINUE;
+ }
+
+
+ protected abstract Request resolveRequest(ServletRequest req);
+
+ @Override
+ public String getAuthMethod() {
+ return "KEYCLOAK-SAML";
+ }
+
+ public static UserIdentity createIdentity(SamlSession samlSession) {
+ Set<String> roles = samlSession.getRoles();
+ if (roles == null) {
+ roles = new HashSet<String>();
+ }
+ Subject theSubject = new Subject();
+ String[] theRoles = new String[roles.size()];
+ roles.toArray(theRoles);
+
+ return new DefaultUserIdentity(theSubject, samlSession.getPrincipal(), theRoles);
+ }
+ public Authentication register(Request request, SamlSession samlSession) {
+ Authentication authentication = request.getAuthentication();
+ if (!(authentication instanceof KeycloakAuthentication)) {
+ UserIdentity userIdentity = createIdentity(samlSession);
+ authentication = createAuthentication(userIdentity);
+ request.setAuthentication(authentication);
+ }
+ return authentication;
+ }
+
+ public abstract Authentication createAuthentication(UserIdentity userIdentity);
+
+ public static abstract class KeycloakAuthentication extends UserAuthentication {
+ public KeycloakAuthentication(String method, UserIdentity userIdentity) {
+ super(method, userIdentity);
+ }
+
+ }
+}
diff --git a/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java
new file mode 100755
index 0000000..6e51f11
--- /dev/null
+++ b/adapters/saml/jetty/jetty-core/src/main/java/org/keycloak/adapters/saml/jetty/JettySamlSessionStore.java
@@ -0,0 +1,163 @@
+package org.keycloak.adapters.saml.jetty;
+
+import org.eclipse.jetty.server.Request;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.AdapterSessionStore;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+import javax.servlet.http.HttpSession;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JettySamlSessionStore implements SamlSessionStore {
+ public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+ private static final Logger log = Logger.getLogger(JettySamlSessionStore.class);
+ private Request request;
+ protected AdapterSessionStore sessionStore;
+ protected HttpFacade facade;
+ protected SessionIdMapper idMapper;
+ protected JettyUserSessionManagement sessionManagement;
+
+ public JettySamlSessionStore(Request request, AdapterSessionStore sessionStore, HttpFacade facade,
+ SessionIdMapper idMapper, JettyUserSessionManagement sessionManagement) {
+ this.request = request;
+ this.sessionStore = sessionStore;
+ this.facade = facade;
+ this.idMapper = idMapper;
+ this.sessionManagement = sessionManagement;
+ }
+
+ @Override
+ public void setCurrentAction(CurrentAction action) {
+ if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+ request.getSession().setAttribute(CURRENT_ACTION, action);
+ }
+
+ @Override
+ public boolean isLoggingIn() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_IN;
+ }
+
+ @Override
+ public boolean isLoggingOut() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_OUT;
+ }
+
+ @Override
+ public void logoutAccount() {
+ HttpSession session = request.getSession(false);
+ if (session != null) {
+ SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession != null) {
+ if (samlSession.getSessionIndex() != null) {
+ idMapper.removeSession(session.getId());
+ }
+ session.removeAttribute(SamlSession.class.getName());
+ }
+ session.removeAttribute(SAML_REDIRECT_URI);
+ }
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+ Set<String> sessions = idMapper.getUserSessions(principal);
+ if (sessions != null) {
+ List<String> ids = new LinkedList<String>();
+ ids.addAll(sessions);
+ logoutSessionIds(ids);
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+ }
+
+ }
+
+ @Override
+ public void logoutBySsoId(List<String> ssoIds) {
+ if (ssoIds == null) return;
+ List<String> sessionIds = new LinkedList<String>();
+ for (String id : ssoIds) {
+ String sessionId = idMapper.getSessionFromSSO(id);
+ if (sessionId != null) {
+ sessionIds.add(sessionId);
+ idMapper.removeSession(sessionId);
+ }
+
+ }
+ logoutSessionIds(sessionIds);
+ }
+
+ protected void logoutSessionIds(List<String> sessionIds) {
+ if (sessionIds == null || sessionIds.isEmpty()) return;
+ sessionManagement.logoutHttpSessions(sessionIds);
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+
+ restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ HttpSession session = request.getSession(true);
+ session.setAttribute(SamlSession.class.getName(), account);
+
+ idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
+
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpSession session = request.getSession(true);
+ return (SamlSession)session.getAttribute(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return (String)request.getSession(true).getAttribute(SAML_REDIRECT_URI);
+ }
+
+ @Override
+ public void saveRequest() {
+ sessionStore.saveRequest();
+
+ request.getSession(true).setAttribute(SAML_REDIRECT_URI, facade.getRequest().getURI());
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return sessionStore.restoreRequest();
+ }
+
+}
adapters/saml/jetty/pom.xml 22(+22 -0)
diff --git a/adapters/saml/jetty/pom.xml b/adapters/saml/jetty/pom.xml
new file mode 100755
index 0000000..a69e7fd
--- /dev/null
+++ b/adapters/saml/jetty/pom.xml
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML Jetty Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-jetty-integration-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>jetty-core</module>
+ <module>jetty8.1</module>
+ <module>jetty9.1</module>
+ <module>jetty9.2</module>
+ </modules>
+</project>
adapters/saml/pom.xml 25(+25 -0)
diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml
new file mode 100755
index 0000000..f34efcb
--- /dev/null
+++ b/adapters/saml/pom.xml
@@ -0,0 +1,25 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML Client Adapter Modules</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-client-adapter-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>core</module>
+ <module>undertow</module>
+ <module>tomcat</module>
+ <module>jetty</module>
+ <module>wildfly</module>
+ <module>as7-eap6</module>
+ <module>servlet-filter</module>
+ </modules>
+</project>
adapters/saml/servlet-filter/pom.xml 69(+69 -0)
diff --git a/adapters/saml/servlet-filter/pom.xml b/adapters/saml/servlet-filter/pom.xml
new file mode 100755
index 0000000..9fefcce
--- /dev/null
+++ b/adapters/saml/servlet-filter/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
+ <name>Keycloak SAML Servlet Filter</name>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-servlet-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java b/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
new file mode 100755
index 0000000..e690db5
--- /dev/null
+++ b/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/FilterSamlSessionStore.java
@@ -0,0 +1,151 @@
+package org.keycloak.adapters.saml.servlet;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.KeycloakAccount;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.servlet.FilterSessionStore;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FilterSamlSessionStore extends FilterSessionStore implements SamlSessionStore {
+ protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+ protected final SessionIdMapper idMapper;
+
+ public FilterSamlSessionStore(HttpServletRequest request, HttpFacade facade, int maxBuffer, SessionIdMapper idMapper) {
+ super(request, facade, maxBuffer);
+ this.idMapper = idMapper;
+ }
+
+ @Override
+ public void setCurrentAction(CurrentAction action) {
+ if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+ request.getSession().setAttribute(CURRENT_ACTION, action);
+ }
+
+ @Override
+ public boolean isLoggingIn() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_IN;
+ }
+
+ @Override
+ public boolean isLoggingOut() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_OUT;
+ }
+
+ @Override
+ public void logoutAccount() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return;
+ if (session != null) {
+ if (idMapper != null) idMapper.removeSession(session.getId());
+ SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession != null) {
+ session.removeAttribute(SamlSession.class.getName());
+ }
+ clearSavedRequest(session);
+ }
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+ SamlSession account = getAccount();
+ if (account != null && account.getPrincipal().getSamlSubject().equals(principal)) {
+ logoutAccount();
+ }
+ if (idMapper != null) {
+ Set<String> sessions = idMapper.getUserSessions(principal);
+ if (sessions != null) {
+ List<String> ids = new LinkedList<String>();
+ ids.addAll(sessions);
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public void logoutBySsoId(List<String> ssoIds) {
+ SamlSession account = getAccount();
+ for (String ssoId : ssoIds) {
+ if (account != null && account.getSessionIndex().equals(ssoId)) {
+ logoutAccount();
+ } else if (idMapper != null) {
+ String sessionId = idMapper.getSessionFromSSO(ssoId);
+ idMapper.removeSession(sessionId);
+ }
+ }
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+ if (idMapper != null && !idMapper.hasSession(session.getId())) {
+ logoutAccount();
+ return false;
+ }
+
+ needRequestRestore = restoreRequest();
+ return true;
+ }
+
+ public HttpServletRequestWrapper getWrap() {
+ HttpSession session = request.getSession(true);
+ final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ final KeycloakAccount account = samlSession;
+ return buildWrapper(session, account);
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ HttpSession session = request.getSession(true);
+ session.setAttribute(SamlSession.class.getName(), account);
+ if (idMapper != null) idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return null;
+ return (SamlSession)session.getAttribute(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return null;
+ return (String)session.getAttribute(REDIRECT_URI);
+ }
+
+}
diff --git a/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java b/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
new file mode 100755
index 0000000..ac95784
--- /dev/null
+++ b/adapters/saml/servlet-filter/src/main/java/org/keycloak/adapters/saml/servlet/SamlFilter.java
@@ -0,0 +1,165 @@
+package org.keycloak.adapters.saml.servlet;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+import org.keycloak.adapters.servlet.ServletHttpFacade;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlFilter implements Filter {
+ protected SamlDeploymentContext deploymentContext;
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+ private final static Logger log = Logger.getLogger("" + SamlFilter.class);
+
+ @Override
+ public void init(final FilterConfig filterConfig) throws ServletException {
+ String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
+ if (configResolverClass != null) {
+ try {
+ throw new RuntimeException("Not implemented yet");
+ // KeycloakConfigResolver configResolver = (KeycloakConfigResolver)
+ // context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
+ // deploymentContext = new SamlDeploymentContext(configResolver);
+ // log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.",
+ // configResolverClass);
+ } catch (Exception ex) {
+ log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[] { configResolverClass, ex.getMessage() });
+ // deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ String fp = filterConfig.getInitParameter("keycloak.config.file");
+ InputStream is = null;
+ if (fp != null) {
+ try {
+ is = new FileInputStream(fp);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ String path = "/WEB-INF/keycloak-saml.xml";
+ String pathParam = filterConfig.getInitParameter("keycloak.config.path");
+ if (pathParam != null)
+ path = pathParam;
+ is = filterConfig.getServletContext().getResourceAsStream(path);
+ }
+ final SamlDeployment deployment;
+ if (is == null) {
+ log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ deployment = new DefaultSamlDeployment();
+ } else {
+ try {
+ ResourceLoader loader = new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ return filterConfig.getServletContext().getResourceAsStream(resource);
+ }
+ };
+ deployment = new DeploymentBuilder().build(is, loader);
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ deploymentContext = new SamlDeploymentContext(deployment);
+ log.fine("Keycloak is using a per-deployment configuration.");
+ }
+ filterConfig.getServletContext().setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+
+ }
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) req;
+ HttpServletResponse response = (HttpServletResponse) res;
+ ServletHttpFacade facade = new ServletHttpFacade(request, response);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment == null || !deployment.isConfigured()) {
+ response.sendError(403);
+ log.fine("deployment not configured");
+ return;
+ }
+ FilterSamlSessionStore tokenStore = new FilterSamlSessionStore(request, facade, 100000, idMapper);
+
+ SamlAuthenticator authenticator = new SamlAuthenticator(facade, deployment, tokenStore) {
+ @Override
+ protected void completeAuthentication(SamlSession account) {
+
+ }
+ };
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ log.fine("AUTHENTICATED");
+ if (facade.isEnded()) {
+ return;
+ }
+ HttpServletRequestWrapper wrapper = tokenStore.getWrap();
+ chain.doFilter(wrapper, res);
+ return;
+ }
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ tokenStore.logoutAccount();
+ if (deployment.getLogoutPage() != null) {
+ RequestDispatcher disp = req.getRequestDispatcher(deployment.getLogoutPage());
+ disp.forward(req, res);
+ return;
+ }
+ chain.doFilter(req, res);
+ return;
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ log.fine("challenge");
+ challenge.challenge(facade);
+ return;
+ }
+
+ if (deployment.isIsPassive() && outcome == AuthOutcome.NOT_AUTHENTICATED) {
+ log.fine("PASSIVE_NOT_AUTHENTICATED");
+ if (facade.isEnded()) {
+ return;
+ }
+ chain.doFilter(req, res);
+ return;
+ }
+
+ if (!facade.isEnded()) {
+ response.sendError(403);
+ }
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
adapters/saml/tomcat/pom.xml 22(+22 -0)
diff --git a/adapters/saml/tomcat/pom.xml b/adapters/saml/tomcat/pom.xml
new file mode 100755
index 0000000..5bc89b4
--- /dev/null
+++ b/adapters/saml/tomcat/pom.xml
@@ -0,0 +1,22 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML Tomcat Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>tomcat-core</module>
+ <module>tomcat6</module>
+ <module>tomcat7</module>
+ <module>tomcat8</module>
+ </modules>
+</project>
adapters/saml/tomcat/tomcat6/pom.xml 76(+76 -0)
diff --git a/adapters/saml/tomcat/tomcat6/pom.xml b/adapters/saml/tomcat/tomcat6/pom.xml
new file mode 100755
index 0000000..38728cf
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat6/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-tomcat6-adapter</artifactId>
+ <name>Keycloak Tomcat 6 Saml Integration</name>
+ <properties>
+ <tomcat.version>6.0.41</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/tomcat/tomcat6/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat6/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
new file mode 100755
index 0000000..1541038
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat6/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
@@ -0,0 +1,60 @@
+package org.keycloak.adapters.saml.tomcat;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
+ @Override
+ public boolean authenticate(Request request, Response response, LoginConfig config) throws IOException {
+ return authenticateInternal(request, response, config);
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ forwardToErrorPage(request, (Response)response, config);
+ return true;
+ }
+
+
+ @Override
+ public void start() throws LifecycleException {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ super.start();
+ }
+
+ public void logout(Request request) throws ServletException {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(null, userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/saml/tomcat/tomcat7/pom.xml 84(+84 -0)
diff --git a/adapters/saml/tomcat/tomcat7/pom.xml b/adapters/saml/tomcat/tomcat7/pom.xml
new file mode 100755
index 0000000..5191ae1
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat7/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-tomcat7-adapter</artifactId>
+ <name>Keycloak Tomcat 7 SAML Integration</name>
+ <properties>
+ <!--<tomcat.version>8.0.14</tomcat.version>-->
+ <tomcat.version>7.0.52</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/tomcat/tomcat7/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat7/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
new file mode 100755
index 0000000..54f9a42
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat7/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
@@ -0,0 +1,56 @@
+package org.keycloak.adapters.saml.tomcat;
+
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.deploy.LoginConfig;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
+ public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException {
+ return authenticateInternal(request, response, config);
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ forwardToErrorPage(request, (Response)response, config);
+ return true;
+ }
+
+
+ protected void initInternal() {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ }
+
+ public void logout(Request request) throws ServletException {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/saml/tomcat/tomcat8/pom.xml 83(+83 -0)
diff --git a/adapters/saml/tomcat/tomcat8/pom.xml b/adapters/saml/tomcat/tomcat8/pom.xml
new file mode 100755
index 0000000..b9c37d3
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat8/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-tomcat8-adapter</artifactId>
+ <name>Keycloak Tomcat 8 SAML Integration</name>
+ <properties>
+ <tomcat.version>8.0.14</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-catalina</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/tomcat/tomcat8/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat8/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
new file mode 100755
index 0000000..ead126a
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat8/src/main/java/org/keycloak/adapters/saml/tomcat/SamlAuthenticatorValve.java
@@ -0,0 +1,71 @@
+package org.keycloak.adapters.saml.tomcat;
+
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.apache.tomcat.util.descriptor.web.LoginConfig;
+import org.keycloak.adapters.saml.AbstractSamlAuthenticatorValve;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.List;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
+ public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
+ return authenticateInternal(request, response, request.getContext().getLoginConfig());
+ }
+
+ @Override
+ protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ if (loginConfig == null) return false;
+ LoginConfig config = (LoginConfig)loginConfig;
+ if (config.getErrorPage() == null) return false;
+ // had to do this to get around compiler/IDE issues :(
+ try {
+ Method method = null;
+ /*
+ for (Method m : getClass().getDeclaredMethods()) {
+ if (m.getName().equals("forwardToErrorPage")) {
+ method = m;
+ break;
+ }
+ }
+ */
+ method = FormAuthenticator.class.getDeclaredMethod("forwardToErrorPage", Request.class, HttpServletResponse.class, LoginConfig.class);
+ method.setAccessible(true);
+ method.invoke(this, request, response, config);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+
+ protected void initInternal() {
+ StandardContext standardContext = (StandardContext) context;
+ standardContext.addLifecycleListener(this);
+ }
+
+ public void logout(Request request) {
+ logoutInternal(request);
+ }
+
+ @Override
+ protected GenericPrincipalFactory createPrincipalFactory() {
+ return new GenericPrincipalFactory() {
+ @Override
+ protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
+ return new GenericPrincipal(userPrincipal.getName(), null, roles, userPrincipal, null);
+ }
+ };
+ }
+}
adapters/saml/tomcat/tomcat-core/pom.xml 89(+89 -0)
diff --git a/adapters/saml/tomcat/tomcat-core/pom.xml b/adapters/saml/tomcat/tomcat-core/pom.xml
new file mode 100755
index 0000000..fa60cb0
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat-core/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-tomcat-adapter-core</artifactId>
+ <name>Keycloak Tomcat Core SAML Integration</name>
+ <properties>
+ <!-- <tomcat.version>8.0.14</tomcat.version> -->
+ <!-- <tomcat.version>7.0.52</tomcat.version> -->
+ <tomcat.version>6.0.41</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk15on</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <!--
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
new file mode 100755
index 0000000..3119ba7
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/AbstractSamlAuthenticatorValve.java
@@ -0,0 +1,244 @@
+package org.keycloak.adapters.saml;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.authenticator.FormAuthenticator;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+import org.keycloak.adapters.tomcat.CatalinaHttpFacade;
+import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+import org.keycloak.saml.common.exceptions.ParsingException;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Keycloak authentication valve
+ *
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractSamlAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
+
+ public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
+
+ private final static Logger log = Logger.getLogger(""+AbstractSamlAuthenticatorValve.class);
+ protected CatalinaUserSessionManagement userSessionManagement = new CatalinaUserSessionManagement();
+ protected SamlDeploymentContext deploymentContext;
+ protected SessionIdMapper mapper = new InMemorySessionIdMapper();
+
+ @Override
+ public void lifecycleEvent(LifecycleEvent event) {
+ if (Lifecycle.START_EVENT.equals(event.getType())) {
+ cache = false;
+ } else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
+ keycloakInit();
+ } else if (event.getType() == Lifecycle.BEFORE_STOP_EVENT) {
+ beforeStop();
+ }
+ }
+
+ protected void logoutInternal(Request request) {
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(null, request);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ SamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
+ tokenStore.logoutAccount();
+ request.setUserPrincipal(null);
+ }
+
+ @SuppressWarnings("UseSpecificCatch")
+ public void keycloakInit() {
+ // Possible scenarios:
+ // 1) The deployment has a keycloak.config.resolver specified and it exists:
+ // Outcome: adapter uses the resolver
+ // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+ // Outcome: adapter is left unconfigured
+ // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+ // Outcome: adapter uses it
+ // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+ // Outcome: adapter is left unconfigured
+
+ String configResolverClass = context.getServletContext().getInitParameter("keycloak.config.resolver");
+ if (configResolverClass != null) {
+ try {
+ throw new RuntimeException("Not implemented yet");
+ //KeycloakConfigResolver configResolver = (KeycloakConfigResolver) context.getLoader().getClassLoader().loadClass(configResolverClass).newInstance();
+ //deploymentContext = new SamlDeploymentContext(configResolver);
+ //log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
+ } catch (Exception ex) {
+ log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
+ //deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ InputStream is = getConfigInputStream(context);
+ final SamlDeployment deployment;
+ if (is == null) {
+ log.info("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ deployment = new DefaultSamlDeployment();
+ } else {
+ try {
+ ResourceLoader loader = new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ return context.getServletContext().getResourceAsStream(resource);
+ }
+ };
+ deployment = new DeploymentBuilder().build(is, loader);
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ deploymentContext = new SamlDeploymentContext(deployment);
+ log.fine("Keycloak is using a per-deployment configuration.");
+ }
+
+ context.getServletContext().setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+ }
+
+ protected void beforeStop() {
+ }
+
+ private static InputStream getConfigFromServletContext(ServletContext servletContext) {
+ String xml = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
+ if (xml == null) {
+ return null;
+ }
+ log.finest("**** using " + AdapterConstants.AUTH_DATA_PARAM_NAME);
+ log.finest(xml);
+ return new ByteArrayInputStream(xml.getBytes());
+ }
+
+ private static InputStream getConfigInputStream(Context context) {
+ InputStream is = getConfigFromServletContext(context.getServletContext());
+ if (is == null) {
+ String path = context.getServletContext().getInitParameter("keycloak.config.file");
+ if (path == null) {
+ log.fine("**** using /WEB-INF/keycloak-saml.xml");
+ is = context.getServletContext().getResourceAsStream("/WEB-INF/keycloak-saml.xml");
+ } else {
+ try {
+ is = new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ log.log(Level.SEVERE, "NOT FOUND {0}", path);
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return is;
+ }
+
+ @Override
+ public void invoke(Request request, Response response) throws IOException, ServletException {
+ log.fine("*********************** SAML ************");
+ try {
+ super.invoke(request, response);
+ } finally {
+ }
+ }
+
+ protected abstract GenericPrincipalFactory createPrincipalFactory();
+ protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
+ protected void forwardToLogoutPage(Request request, HttpServletResponse response,SamlDeployment deployment) {
+ RequestDispatcher disp = request.getRequestDispatcher(deployment.getLogoutPage());
+ //make sure the login page is never cached
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ response.setHeader("Pragma", "no-cache");
+ response.setHeader("Expires", "0");
+
+
+ try {
+ disp.forward(request.getRequest(), response);
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
+ log.fine("authenticateInternal");
+ CatalinaHttpFacade facade = new CatalinaHttpFacade(response, request);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (deployment == null || !deployment.isConfigured()) {
+ log.fine("deployment not configured");
+ return false;
+ }
+ SamlSessionStore tokenStore = getTokenStore(request, facade, deployment);
+
+
+ CatalinaSamlAuthenticator authenticator = new CatalinaSamlAuthenticator(facade, deployment, tokenStore);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ log.fine("AUTHENTICATED");
+ if (facade.isEnded()) {
+ return false;
+ }
+ return true;
+ }
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ logoutInternal(request);
+ if (deployment.getLogoutPage() != null) {
+ forwardToLogoutPage(request, response, deployment);
+
+ }
+ log.fine("Logging OUT");
+ return false;
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ log.fine("challenge");
+ if (loginConfig == null) {
+ loginConfig = request.getContext().getLoginConfig();
+ }
+ challenge.challenge(facade);
+ }
+ return false;
+ }
+
+ public void keycloakSaveRequest(Request request) throws IOException {
+ saveRequest(request, request.getSessionInternal(true));
+ }
+
+ public boolean keycloakRestoreRequest(Request request) {
+ try {
+ return restoreRequest(request, request.getSessionInternal());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected SamlSessionStore getTokenStore(Request request, HttpFacade facade, SamlDeployment resolvedDeployment) {
+ SamlSessionStore store = (SamlSessionStore)request.getNote(TOKEN_STORE_NOTE);
+ if (store != null) {
+ return store;
+ }
+
+ store = new CatalinaSamlSessionStore(userSessionManagement, createPrincipalFactory(), mapper, request, this, facade);
+
+ request.setNote(TOKEN_STORE_NOTE, store);
+ return store;
+ }
+
+}
diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlAuthenticator.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlAuthenticator.java
new file mode 100755
index 0000000..b991124
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlAuthenticator.java
@@ -0,0 +1,18 @@
+package org.keycloak.adapters.saml;
+
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaSamlAuthenticator extends SamlAuthenticator {
+ public CatalinaSamlAuthenticator(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ super(facade, deployment, sessionStore);
+ }
+
+ @Override
+ protected void completeAuthentication(SamlSession account) {
+ // complete
+ }
+}
diff --git a/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java
new file mode 100755
index 0000000..804acf3
--- /dev/null
+++ b/adapters/saml/tomcat/tomcat-core/src/main/java/org/keycloak/adapters/saml/CatalinaSamlSessionStore.java
@@ -0,0 +1,214 @@
+package org.keycloak.adapters.saml;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.tomcat.CatalinaUserSessionManagement;
+import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaSamlSessionStore implements SamlSessionStore {
+ protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+ public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+
+ private final CatalinaUserSessionManagement sessionManagement;
+ protected final GenericPrincipalFactory principalFactory;
+ private final SessionIdMapper idMapper;
+ protected final Request request;
+ protected final AbstractSamlAuthenticatorValve valve;
+ protected final HttpFacade facade;
+
+ public CatalinaSamlSessionStore(CatalinaUserSessionManagement sessionManagement, GenericPrincipalFactory principalFactory,
+ SessionIdMapper idMapper, Request request, AbstractSamlAuthenticatorValve valve, HttpFacade facade) {
+ this.sessionManagement = sessionManagement;
+ this.principalFactory = principalFactory;
+ this.idMapper = idMapper;
+ this.request = request;
+ this.valve = valve;
+ this.facade = facade;
+ }
+
+ @Override
+ public void setCurrentAction(CurrentAction action) {
+ if (action == CurrentAction.NONE && request.getSession(false) == null) return;
+ request.getSession().setAttribute(CURRENT_ACTION, action);
+ }
+
+ @Override
+ public boolean isLoggingIn() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_IN;
+ }
+
+ @Override
+ public boolean isLoggingOut() {
+ HttpSession session = request.getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_OUT;
+ }
+
+ @Override
+ public void logoutAccount() {
+ Session sessionInternal = request.getSessionInternal(false);
+ if (sessionInternal == null) return;
+ HttpSession session = sessionInternal.getSession();
+ if (session != null) {
+ SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession != null) {
+ if (samlSession.getSessionIndex() != null) {
+ idMapper.removeSession(session.getId());
+ }
+ session.removeAttribute(SamlSession.class.getName());
+ }
+ session.removeAttribute(SAML_REDIRECT_URI);
+ }
+ sessionInternal.setPrincipal(null);
+ sessionInternal.setAuthType(null);
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+ Set<String> sessions = idMapper.getUserSessions(principal);
+ if (sessions != null) {
+ List<String> ids = new LinkedList<String>();
+ ids.addAll(sessions);
+ logoutSessionIds(ids);
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+ }
+
+ }
+
+ @Override
+ public void logoutBySsoId(List<String> ssoIds) {
+ if (ssoIds == null) return;
+ List<String> sessionIds = new LinkedList<String>();
+ for (String id : ssoIds) {
+ String sessionId = idMapper.getSessionFromSSO(id);
+ if (sessionId != null) {
+ sessionIds.add(sessionId);
+ idMapper.removeSession(sessionId);
+ }
+
+ }
+ logoutSessionIds(sessionIds);
+ }
+
+ protected void logoutSessionIds(List<String> sessionIds) {
+ if (sessionIds == null || sessionIds.isEmpty()) return;
+ Manager sessionManager = request.getContext().getManager();
+ sessionManagement.logoutHttpSessions(sessionManager, sessionIds);
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ Session session = request.getSessionInternal(false);
+ if (session == null) return false;
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ final SamlSession samlSession = (SamlSession)session.getSession().getAttribute(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+
+ GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+ if (samlSession.getPrincipal().getName().equals(principal.getName()))
+ // in clustered environment in JBossWeb, principal is not serialized or saved
+ if (principal == null) {
+ principal = principalFactory.createPrincipal(request.getContext().getRealm(), samlSession.getPrincipal(), samlSession.getRoles());
+ session.setPrincipal(principal);
+ session.setAuthType("KEYCLOAK-SAML");
+
+ } else {
+ if (!principal.getUserPrincipal().getName().equals(samlSession.getPrincipal().getName())) {
+ throw new RuntimeException("Unknown State");
+ }
+ log.debug("************principal already in");
+ if (log.isDebugEnabled()) {
+ for (String role : principal.getRoles()) {
+ log.debug("principal role: " + role);
+ }
+ }
+
+ }
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK-SAML");
+ restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ Session session = request.getSessionInternal(true);
+ session.getSession().setAttribute(SamlSession.class.getName(), account);
+ GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+ // in clustered environment in JBossWeb, principal is not serialized or saved
+ if (principal == null) {
+ principal = principalFactory.createPrincipal(request.getContext().getRealm(), account.getPrincipal(), account.getRoles());
+ session.setPrincipal(principal);
+ session.setAuthType("KEYCLOAK-SAML");
+
+ }
+ request.setUserPrincipal(principal);
+ request.setAuthType("KEYCLOAK-SAML");
+ idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
+
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpSession session = getSession(true);
+ return (SamlSession)session.getAttribute(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ return (String)getSession(true).getAttribute(SAML_REDIRECT_URI);
+ }
+
+ @Override
+ public void saveRequest() {
+ try {
+ valve.keycloakSaveRequest(request);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ getSession(true).setAttribute(SAML_REDIRECT_URI, facade.getRequest().getURI());
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ getSession(true).removeAttribute(SAML_REDIRECT_URI);
+ return valve.keycloakRestoreRequest(request);
+ }
+
+ protected HttpSession getSession(boolean create) {
+ Session session = request.getSessionInternal(create);
+ if (session == null) return null;
+ return session.getSession();
+ }
+}
adapters/saml/undertow/pom.xml 87(+87 -0)
diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml
new file mode 100755
index 0000000..3e2c883
--- /dev/null
+++ b/adapters/saml/undertow/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-undertow-adapter</artifactId>
+ <name>Keycloak Undertow SAML Adapter</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
new file mode 100755
index 0000000..3d632dd
--- /dev/null
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/AbstractSamlAuthMech.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.saml.undertow;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.NotificationReceiver;
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.api.SecurityNotification;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
+import io.undertow.util.StatusCodes;
+
+/**
+ * Abstract base class for a Keycloak-enabled Undertow AuthenticationMechanism.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public abstract class AbstractSamlAuthMech implements AuthenticationMechanism {
+ public static final AttachmentKey<AuthChallenge> KEYCLOAK_CHALLENGE_ATTACHMENT_KEY = AttachmentKey.create(AuthChallenge.class);
+ protected SamlDeploymentContext deploymentContext;
+ protected UndertowUserSessionManagement sessionManagement;
+ protected String errorPage;
+
+ public AbstractSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
+ this.deploymentContext = deploymentContext;
+ this.sessionManagement = sessionManagement;
+ this.errorPage = errorPage;
+ }
+
+ @Override
+ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) {
+ AuthChallenge challenge = exchange.getAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY);
+ if (challenge != null) {
+ UndertowHttpFacade facade = createFacade(exchange);
+ if (challenge.challenge(facade)) {
+ return new ChallengeResult(true, exchange.getResponseCode());
+ }
+ }
+ return new ChallengeResult(false);
+ }
+
+ protected Integer servePage(final HttpServerExchange exchange, final String location) {
+ sendRedirect(exchange, location);
+ return StatusCodes.TEMPORARY_REDIRECT;
+ }
+
+ static void sendRedirect(final HttpServerExchange exchange, final String location) {
+ // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better
+ // handle this.
+ String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
+ exchange.getResponseHeaders().put(Headers.LOCATION, loc);
+ }
+
+ protected void registerNotifications(final SecurityContext securityContext) {
+
+ final NotificationReceiver logoutReceiver = new NotificationReceiver() {
+ @Override
+ public void handleNotification(SecurityNotification notification) {
+ if (notification.getEventType() != SecurityNotification.EventType.LOGGED_OUT)
+ return;
+
+ HttpServerExchange exchange = notification.getExchange();
+ UndertowHttpFacade facade = createFacade(exchange);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+ sessionStore.logoutAccount();
+ }
+ };
+
+ securityContext.registerNotificationReceiver(logoutReceiver);
+ }
+
+ /**
+ * Call this inside your authenticate method.
+ */
+ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) {
+ UndertowHttpFacade facade = createFacade(exchange);
+ SamlDeployment deployment = deploymentContext.resolveDeployment(facade);
+ if (!deployment.isConfigured()) {
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ SamlSessionStore sessionStore = getTokenStore(exchange, facade, deployment, securityContext);
+ UndertowSamlAuthenticator authenticator = new UndertowSamlAuthenticator(securityContext, facade, deploymentContext.resolveDeployment(facade), sessionStore);
+ AuthOutcome outcome = authenticator.authenticate();
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ registerNotifications(securityContext);
+ return AuthenticationMechanismOutcome.AUTHENTICATED;
+ }
+ if (outcome == AuthOutcome.NOT_AUTHENTICATED) {
+ // we are in passive mode and user is not authenticated, let app server to try another auth mechanism
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ securityContext.logout();
+ if (deployment.getLogoutPage() != null) {
+ redirectLogout(deployment, exchange);
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+ AuthChallenge challenge = authenticator.getChallenge();
+ if (challenge != null) {
+ exchange.putAttachment(KEYCLOAK_CHALLENGE_ATTACHMENT_KEY, challenge);
+ }
+
+ if (outcome == AuthOutcome.FAILED) {
+ return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
+ }
+ return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
+ }
+
+ protected void redirectLogout(SamlDeployment deployment, HttpServerExchange exchange) {
+ String page = deployment.getLogoutPage();
+ sendRedirect(exchange, page);
+ exchange.setResponseCode(302);
+ exchange.endExchange();
+ }
+
+ protected UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+ return new UndertowHttpFacade(exchange);
+ }
+
+ protected abstract SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext);
+}
\ No newline at end of file
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
new file mode 100755
index 0000000..ad22718
--- /dev/null
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/SamlServletExtension.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.AuthenticationMechanism;
+import io.undertow.security.api.AuthenticationMechanismFactory;
+import io.undertow.security.idm.Account;
+import io.undertow.security.idm.Credential;
+import io.undertow.security.idm.IdentityManager;
+import io.undertow.server.handlers.form.FormParserFactory;
+import io.undertow.servlet.ServletExtension;
+import io.undertow.servlet.api.AuthMethodConfig;
+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;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.config.parsers.DeploymentBuilder;
+import org.keycloak.adapters.saml.config.parsers.ResourceLoader;
+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;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlServletExtension implements ServletExtension {
+
+ protected static Logger log = Logger.getLogger(SamlServletExtension.class);
+
+ // todo when this DeploymentInfo method of the same name is fixed.
+ public boolean isAuthenticationMechanismPresent(DeploymentInfo deploymentInfo, final String mechanismName) {
+ LoginConfig loginConfig = deploymentInfo.getLoginConfig();
+ if (loginConfig != null) {
+ for (AuthMethodConfig method : loginConfig.getAuthMethods()) {
+ if (method.getName().equalsIgnoreCase(mechanismName)) {
+ return true;
+ }
+ }
+ }
+ 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 = getXMLFromServletContext(context);
+ if (is == null) {
+ String path = context.getInitParameter("keycloak.config.file");
+ if (path == null) {
+ log.debug("using /WEB-INF/keycloak-saml.xml");
+ is = context.getResourceAsStream("/WEB-INF/keycloak-saml.xml");
+ } else {
+ try {
+ is = new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return is;
+ }
+
+
+ @Override
+ @SuppressWarnings("UseSpecificCatch")
+ public void handleDeployment(DeploymentInfo deploymentInfo, final ServletContext servletContext) {
+ if (!isAuthenticationMechanismPresent(deploymentInfo, "KEYCLOAK-SAML")) {
+ log.debug("auth-method is not keycloak saml!");
+ return;
+ }
+ log.debug("SamlServletException initialization");
+
+ // Possible scenarios:
+ // 1) The deployment has a keycloak.config.resolver specified and it exists:
+ // Outcome: adapter uses the resolver
+ // 2) The deployment has a keycloak.config.resolver and isn't valid (doesn't exists, isn't a resolver, ...) :
+ // Outcome: adapter is left unconfigured
+ // 3) The deployment doesn't have a keycloak.config.resolver , but has a keycloak.json (or equivalent)
+ // Outcome: adapter uses it
+ // 4) The deployment doesn't have a keycloak.config.resolver nor keycloak.json (or equivalent)
+ // Outcome: adapter is left unconfigured
+
+ SamlConfigResolver configResolver;
+ String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
+ SamlDeploymentContext deploymentContext = null;
+ if (configResolverClass != null) {
+ try {
+ throw new RuntimeException("Not implemented yet");
+ //configResolver = (SamlConfigResolver) deploymentInfo.getClassLoader().loadClass(configResolverClass).newInstance();
+ //deploymentContext = new AdapterDeploymentContext(configResolver);
+ //log.info("Using " + configResolverClass + " to resolve Keycloak configuration on a per-request basis.");
+ } catch (Exception ex) {
+ log.warn("The specified resolver " + configResolverClass + " could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: " + ex.getMessage());
+ //deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ InputStream is = getConfigInputStream(servletContext);
+ final SamlDeployment deployment;
+ if (is == null) {
+ log.warn("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
+ deployment = new DefaultSamlDeployment();
+ } else {
+ try {
+ ResourceLoader loader = new ResourceLoader() {
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ return servletContext.getResourceAsStream(resource);
+ }
+ };
+ deployment = new DeploymentBuilder().build(is, loader);
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ deploymentContext = new SamlDeploymentContext(deployment);
+ log.debug("Keycloak is using a per-deployment configuration.");
+ }
+
+ servletContext.setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+ UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
+ final ServletSamlAuthMech mech = createAuthMech(deploymentInfo, deploymentContext, userSessionManagement);
+
+
+ // setup handlers
+
+ deploymentInfo.addAuthenticationMechanism("KEYCLOAK-SAML", new AuthenticationMechanismFactory() {
+ @Override
+ public AuthenticationMechanism create(String s, FormParserFactory formParserFactory, Map<String, String> stringStringMap) {
+ return mech;
+ }
+ }); // authentication
+
+ deploymentInfo.setIdentityManager(new IdentityManager() {
+ @Override
+ public Account verify(Account account) {
+ return account;
+ }
+
+ @Override
+ public Account verify(String id, Credential credential) {
+ throw new IllegalStateException("Should never be called in Keycloak flow");
+ }
+
+ @Override
+ public Account verify(Credential credential) {
+ throw new IllegalStateException("Should never be called in Keycloak flow");
+ }
+ });
+
+ log.debug("Setting jsession cookie path to: " + deploymentInfo.getContextPath());
+ ServletSessionConfig cookieConfig = new ServletSessionConfig();
+ cookieConfig.setPath(deploymentInfo.getContextPath());
+ deploymentInfo.setServletSessionConfig(cookieConfig);
+
+ }
+
+ protected ServletSamlAuthMech createAuthMech(DeploymentInfo deploymentInfo, SamlDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement) {
+ return new ServletSamlAuthMech(deploymentContext, userSessionManagement, getErrorPage(deploymentInfo));
+ }
+
+ protected String getErrorPage(DeploymentInfo deploymentInfo) {
+ LoginConfig loginConfig = deploymentInfo.getLoginConfig();
+ String errorPage = null;
+ if (loginConfig != null) {
+ errorPage = loginConfig.getErrorPage();
+ }
+ return errorPage;
+ }
+}
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
new file mode 100755
index 0000000..9fe9085
--- /dev/null
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlAuthMech.java
@@ -0,0 +1,71 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.util.Headers;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.ServletHttpFacade;
+import org.keycloak.adapters.undertow.UndertowHttpFacade;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlAuthMech extends AbstractSamlAuthMech {
+ protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
+ public ServletSamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
+ super(deploymentContext, sessionManagement, errorPage);
+ }
+
+ @Override
+ protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
+ return new ServletSamlSessionStore(exchange, sessionManagement, securityContext, idMapper);
+ }
+
+ @Override
+ protected UndertowHttpFacade createFacade(HttpServerExchange exchange) {
+ return new ServletHttpFacade(exchange);
+ }
+
+ @Override
+ protected void redirectLogout(SamlDeployment deployment, HttpServerExchange exchange) {
+ servePage(exchange, deployment.getLogoutPage());
+ }
+
+ @Override
+ protected Integer servePage(HttpServerExchange exchange, String location) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ ServletRequest req = servletRequestContext.getServletRequest();
+ ServletResponse resp = servletRequestContext.getServletResponse();
+ RequestDispatcher disp = req.getRequestDispatcher(location);
+ //make sure the login page is never cached
+ exchange.getResponseHeaders().add(Headers.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
+ exchange.getResponseHeaders().add(Headers.PRAGMA, "no-cache");
+ exchange.getResponseHeaders().add(Headers.EXPIRES, "0");
+
+
+ try {
+ disp.forward(req, resp);
+ } catch (ServletException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
+ }
+
+
+}
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
new file mode 100755
index 0000000..34d718b
--- /dev/null
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/ServletSamlSessionStore.java
@@ -0,0 +1,213 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.SessionManager;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import io.undertow.servlet.spec.HttpSessionImpl;
+import io.undertow.servlet.util.SavedRequest;
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.keycloak.dom.saml.v2.protocol.StatusType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletSamlSessionStore implements SamlSessionStore {
+ protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+ public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+
+ private final HttpServerExchange exchange;
+ private final UndertowUserSessionManagement sessionManagement;
+ private final SecurityContext securityContext;
+ private final SessionIdMapper idMapper;
+
+
+ public ServletSamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext,
+ SessionIdMapper idMapper) {
+ this.exchange = exchange;
+ this.sessionManagement = sessionManagement;
+ this.securityContext = securityContext;
+ this.idMapper = idMapper;
+ }
+
+ @Override
+ public void setCurrentAction(CurrentAction action) {
+ if (action == CurrentAction.NONE && getRequest().getSession(false) == null) return;
+ getRequest().getSession().setAttribute(CURRENT_ACTION, action);
+ }
+
+ @Override
+ public boolean isLoggingIn() {
+ HttpSession session = getRequest().getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_IN;
+ }
+
+ @Override
+ public boolean isLoggingOut() {
+ HttpSession session = getRequest().getSession(false);
+ if (session == null) return false;
+ CurrentAction action = (CurrentAction)session.getAttribute(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_OUT;
+ }
+
+ @Override
+ public void logoutAccount() {
+ HttpSession session = getSession(false);
+ if (session != null) {
+ SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession != null) {
+ if (samlSession.getSessionIndex() != null) {
+ idMapper.removeSession(session.getId());
+ }
+ session.removeAttribute(SamlSession.class.getName());
+ }
+ session.removeAttribute(SAML_REDIRECT_URI);
+ }
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+ Set<String> sessions = idMapper.getUserSessions(principal);
+ if (sessions != null) {
+ List<String> ids = new LinkedList<>();
+ ids.addAll(sessions);
+ logoutSessionIds(ids);
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+ }
+
+ }
+
+ @Override
+ public void logoutBySsoId(List<String> ssoIds) {
+ if (ssoIds == null) return;
+ List<String> sessionIds = new LinkedList<>();
+ for (String id : ssoIds) {
+ String sessionId = idMapper.getSessionFromSSO(id);
+ if (sessionId != null) {
+ sessionIds.add(sessionId);
+ idMapper.removeSession(sessionId);
+ }
+
+ }
+ logoutSessionIds(sessionIds);
+ }
+
+ protected void logoutSessionIds(List<String> sessionIds) {
+ if (sessionIds == null || sessionIds.isEmpty()) return;
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ SessionManager sessionManager = servletRequestContext.getDeployment().getSessionManager();
+ sessionManagement.logoutHttpSessions(sessionManager, sessionIds);
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ HttpSession session = getSession(false);
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ final SamlSession samlSession = (SamlSession)session.getAttribute(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+
+ Account undertowAccount = new Account() {
+ @Override
+ public Principal getPrincipal() {
+ return samlSession.getPrincipal();
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return samlSession.getRoles();
+ }
+ };
+ securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+ restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSession session = getSession(true);
+ session.setAttribute(SamlSession.class.getName(), account);
+ sessionManagement.login(servletRequestContext.getDeployment().getSessionManager());
+ idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), session.getId());
+
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpSession session = getSession(true);
+ return (SamlSession)session.getAttribute(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+ return (String)session.getAttribute(SAML_REDIRECT_URI);
+ }
+
+ @Override
+ public void saveRequest() {
+ SavedRequest.trySaveRequest(exchange);
+ final ServletRequestContext sc = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ HttpSessionImpl session = sc.getCurrentServletContext().getSession(exchange, true);
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString());
+ if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+ String uri = uriBuilder.build().toString();
+
+ session.setAttribute(SAML_REDIRECT_URI, uri);
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ HttpSession session = getSession(false);
+ if (session == null) return false;
+ SavedRequest.tryRestoreRequest(exchange, session);
+ session.removeAttribute(SAML_REDIRECT_URI);
+ return false;
+ }
+
+ protected HttpSession getSession(boolean create) {
+ HttpServletRequest req = getRequest();
+ return req.getSession(create);
+ }
+
+ private HttpServletResponse getResponse() {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ return (HttpServletResponse)servletRequestContext.getServletResponse();
+
+ }
+
+ private HttpServletRequest getRequest() {
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ return (HttpServletRequest) servletRequestContext.getServletRequest();
+ }
+}
diff --git a/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
new file mode 100755
index 0000000..eac0cf7
--- /dev/null
+++ b/adapters/saml/undertow/src/main/java/org/keycloak/adapters/saml/undertow/UndertowSamlAuthenticator.java
@@ -0,0 +1,42 @@
+package org.keycloak.adapters.saml.undertow;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.security.idm.Account;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+
+import java.security.Principal;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowSamlAuthenticator extends SamlAuthenticator {
+ protected SecurityContext securityContext;
+
+ public UndertowSamlAuthenticator(SecurityContext securityContext, HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ super(facade, deployment, sessionStore);
+ this.securityContext = securityContext;
+ }
+
+ @Override
+ protected void completeAuthentication(final SamlSession samlSession) {
+ Account undertowAccount = new Account() {
+ @Override
+ public Principal getPrincipal() {
+ return samlSession.getPrincipal();
+ }
+
+ @Override
+ public Set<String> getRoles() {
+ return samlSession.getRoles();
+ }
+ };
+ securityContext.authenticationComplete(undertowAccount, "KEYCLOAK-SAML", false);
+
+ }
+}
adapters/saml/wildfly/pom.xml 20(+20 -0)
diff --git a/adapters/saml/wildfly/pom.xml b/adapters/saml/wildfly/pom.xml
new file mode 100755
index 0000000..641da3b
--- /dev/null
+++ b/adapters/saml/wildfly/pom.xml
@@ -0,0 +1,20 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak SAML Wildfly Integration</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-wildfly-integration-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>wildfly-adapter</module>
+ <module>wildfly-subsystem</module>
+ </modules>
+</project>
diff --git a/adapters/saml/wildfly/wildfly-adapter/pom.xml b/adapters/saml/wildfly/wildfly-adapter/pom.xml
new file mode 100755
index 0000000..1b9e720
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/pom.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-saml-wildfly-adapter</artifactId>
+ <name>Keycloak Wildfly SAML Adapter</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-undertow-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jboss-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.picketbox</groupId>
+ <artifactId>picketbox</artifactId>
+ <version>4.0.20.Final</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.spec.javax.servlet</groupId>
+ <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-servlet</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.undertow</groupId>
+ <artifactId>undertow-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
\ No newline at end of file
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/SecurityInfoHelper.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/SecurityInfoHelper.java
new file mode 100755
index 0000000..7170ac6
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/SecurityInfoHelper.java
@@ -0,0 +1,116 @@
+package org.keycloak.adapters.saml.wildfly;
+
+import org.jboss.security.NestableGroup;
+import org.jboss.security.SecurityConstants;
+import org.jboss.security.SecurityContextAssociation;
+import org.jboss.security.SimpleGroup;
+import org.jboss.security.SimplePrincipal;
+import org.keycloak.adapters.spi.KeycloakAccount;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SecurityInfoHelper {
+ public static void propagateSessionInfo(KeycloakAccount account) {
+ Subject subject = new Subject();
+ Set<Principal> principals = subject.getPrincipals();
+ principals.add(account.getPrincipal());
+ Group[] roleSets = getRoleSets(account.getRoles());
+ for (int g = 0; g < roleSets.length; g++) {
+ Group group = roleSets[g];
+ String name = group.getName();
+ Group subjectGroup = createGroup(name, principals);
+ if (subjectGroup instanceof NestableGroup) {
+ /* A NestableGroup only allows Groups to be added to it so we
+ need to add a SimpleGroup to subjectRoles to contain the roles
+ */
+ SimpleGroup tmp = new SimpleGroup("Roles");
+ subjectGroup.addMember(tmp);
+ subjectGroup = tmp;
+ }
+ // Copy the group members to the Subject group
+ Enumeration<? extends Principal> members = group.members();
+ while (members.hasMoreElements()) {
+ Principal role = (Principal) members.nextElement();
+ subjectGroup.addMember(role);
+ }
+ }
+ // add the CallerPrincipal group if none has been added in getRoleSets
+ Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
+ callerGroup.addMember(account.getPrincipal());
+ principals.add(callerGroup);
+ org.jboss.security.SecurityContext sc = SecurityContextAssociation.getSecurityContext();
+ Principal userPrincipal = getPrincipal(subject);
+ sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
+ }
+
+ /**
+ * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
+ * considered or the single subject inside the CallerPrincipal group.
+ *
+ * @param subject
+ * @return the authenticated subject
+ */
+ protected static Principal getPrincipal(Subject subject) {
+ Principal principal = null;
+ Principal callerPrincipal = null;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ if (principals != null && !principals.isEmpty()) {
+ for (Principal p : principals) {
+ if (!(p instanceof Group) && principal == null) {
+ principal = p;
+ }
+ if (p instanceof Group) {
+ Group g = Group.class.cast(p);
+ if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+ Enumeration<? extends Principal> e = g.members();
+ if (e.hasMoreElements())
+ callerPrincipal = e.nextElement();
+ }
+ }
+ }
+ }
+ }
+ return callerPrincipal == null ? principal : callerPrincipal;
+ }
+
+ protected static Group createGroup(String name, Set<Principal> principals) {
+ Group roles = null;
+ Iterator<Principal> iter = principals.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ if ((next instanceof Group) == false)
+ continue;
+ Group grp = (Group) next;
+ if (grp.getName().equals(name)) {
+ roles = grp;
+ break;
+ }
+ }
+ // If we did not find a group create one
+ if (roles == null) {
+ roles = new SimpleGroup(name);
+ principals.add(roles);
+ }
+ return roles;
+ }
+
+ protected static Group[] getRoleSets(Collection<String> roleSet) {
+ SimpleGroup roles = new SimpleGroup("Roles");
+ Group[] roleSets = {roles};
+ for (String role : roleSet) {
+ roles.addMember(new SimplePrincipal(role));
+ }
+ return roleSets;
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlAuthMech.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlAuthMech.java
new file mode 100755
index 0000000..e532233
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlAuthMech.java
@@ -0,0 +1,25 @@
+package org.keycloak.adapters.saml.wildfly;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.saml.undertow.ServletSamlAuthMech;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflySamlAuthMech extends ServletSamlAuthMech {
+ public WildflySamlAuthMech(SamlDeploymentContext deploymentContext, UndertowUserSessionManagement sessionManagement, String errorPage) {
+ super(deploymentContext, sessionManagement, errorPage);
+ }
+
+ @Override
+ protected SamlSessionStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, SamlDeployment deployment, SecurityContext securityContext) {
+ return new WildflySamlSessionStore(exchange, sessionManagement, securityContext, idMapper);
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlExtension.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlExtension.java
new file mode 100755
index 0000000..191365f
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlExtension.java
@@ -0,0 +1,18 @@
+package org.keycloak.adapters.saml.wildfly;
+
+import io.undertow.servlet.api.DeploymentInfo;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.undertow.SamlServletExtension;
+import org.keycloak.adapters.saml.undertow.ServletSamlAuthMech;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflySamlExtension extends SamlServletExtension {
+ @Override
+ protected ServletSamlAuthMech createAuthMech(DeploymentInfo deploymentInfo, SamlDeploymentContext deploymentContext, UndertowUserSessionManagement userSessionManagement) {
+ return new WildflySamlAuthMech(deploymentContext, userSessionManagement, getErrorPage(deploymentInfo));
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlSessionStore.java b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlSessionStore.java
new file mode 100755
index 0000000..4551333
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/java/org/keycloak/adapters/saml/wildfly/WildflySamlSessionStore.java
@@ -0,0 +1,36 @@
+package org.keycloak.adapters.saml.wildfly;
+
+import io.undertow.security.api.SecurityContext;
+import io.undertow.server.HttpServerExchange;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.undertow.ServletSamlSessionStore;
+import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WildflySamlSessionStore extends ServletSamlSessionStore {
+ public WildflySamlSessionStore(HttpServerExchange exchange, UndertowUserSessionManagement sessionManagement,
+ SecurityContext securityContext, SessionIdMapper idMapper) {
+ super(exchange, sessionManagement, securityContext, idMapper);
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ if (super.isLoggedIn()) {
+ SecurityInfoHelper.propagateSessionInfo(getAccount());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ super.saveAccount(account);
+ SecurityInfoHelper.propagateSessionInfo(account);
+ }
+
+
+}
diff --git a/adapters/saml/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension b/adapters/saml/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
new file mode 100755
index 0000000..f61d13c
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-adapter/src/main/resources/META-INF/services/io.undertow.servlet.ServletExtension
@@ -0,0 +1 @@
+org.keycloak.adapters.saml.wildfly.WildflySamlExtension
adapters/saml/wildfly/wildfly-subsystem/pom.xml 105(+105 -0)
diff --git a/adapters/saml/wildfly/wildfly-subsystem/pom.xml b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
new file mode 100755
index 0000000..8aadd05
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-saml-wildfly-subsystem</artifactId>
+ <name>Keycloak Wildfly SAML Adapter Subsystem</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>false</redirectTestOutputToFile>
+ <enableAssertions>true</enableAssertions>
+ <systemProperties>
+ <property>
+ <name>jboss.home</name>
+ <value>${jboss.home}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-controller</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-web-common</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-annotations</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-processor</artifactId>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-subsystem-test-framework</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-wildfly-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Configuration.java
new file mode 100644
index 0000000..e364e2d
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/Constants.java
new file mode 100644
index 0000000..9b89fb2
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderAddHandler.java
new file mode 100644
index 0000000..7648ba3
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/IdentityProviderDefinition.java
new file mode 100644
index 0000000..1459f15
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyAddHandler.java
new file mode 100644
index 0000000..1799290
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java
new file mode 100755
index 0000000..2c0aa1e
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakAdapterConfigDeploymentProcessor.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.saml.extension;
+
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.common.WarMetaData;
+import org.jboss.dmr.ModelNode;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.jboss.staxmapper.FormattingXMLStreamWriter;
+import org.jboss.staxmapper.XMLExtendedStreamWriter;
+import org.keycloak.adapters.saml.AdapterConstants;
+import org.keycloak.subsystem.adapter.saml.extension.logging.KeycloakLogger;
+
+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;
+
+/**
+ * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ String deploymentName = deploymentUnit.getName();
+ if (Configuration.INSTANCE.getSecureDeployment(deploymentName) != null) {
+ addKeycloakSamlAuthData(phaseContext, deploymentName);
+ }
+ }
+
+ private void addKeycloakSamlAuthData(DeploymentPhaseContext phaseContext, String deploymentName) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
+ }
+
+ try {
+ addXMLData(getXML(deploymentName), warMetaData);
+ } catch (Exception e) {
+ throw new DeploymentUnitProcessingException("Failed to configure KeycloakSamlExtension from subsystem model", e);
+ }
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null) {
+ loginConfig = new LoginConfigMetaData();
+ webMetaData.setLoginConfig(loginConfig);
+ }
+ loginConfig.setAuthMethod("KEYCLOAK-SAML");
+
+ KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java
new file mode 100755
index 0000000..6d30764
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessor.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.saml.extension;
+
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ // Next phase, need to detect if this is a Keycloak deployment. If not, don't add the modules.
+
+ final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+ final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+ addCommonModules(moduleSpecification, moduleLoader);
+ addPlatformSpecificModules(moduleSpecification, moduleLoader);
+ }
+
+ private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
+ }
+
+ abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java
new file mode 100755
index 0000000..5de5c67
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakDependencyProcessorWildFly.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.keycloak.subsystem.adapter.saml.extension;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * Add platform-specific modules for WildFly.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-wildfly-adapter");
+ private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-saml-undertow-adapter");
+
+ @Override
+ protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java
new file mode 100755
index 0000000..bddbce2
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSamlExtension.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.adapter.saml.extension;
+
+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;
+import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
+import org.jboss.as.controller.parsing.ExtensionParsingContext;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
+
+
+/**
+ * Main Extension class for the subsystem.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSamlExtension implements Extension {
+
+ public static final String SUBSYSTEM_NAME = "keycloak-saml";
+ 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 ModelVersion MGMT_API_VERSION = ModelVersion.create(1, 1, 0);
+ static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+
+ public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
+ StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
+ for (String kp : keyPrefix) {
+ prefix.append('.').append(kp);
+ }
+ return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakSamlExtension.class.getClassLoader(), true, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeParsers(final ExtensionParsingContext context) {
+ context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakSamlExtension.NAMESPACE, PARSER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(final ExtensionContext context) {
+ final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION);
+
+ 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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java
new file mode 100755
index 0000000..dbd58b4
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemAdd.java
@@ -0,0 +1,57 @@
+/*
+ * 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.controller.AbstractBoottimeAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+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;
+
+/**
+ * The Keycloak subsystem add update handler.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
+
+ static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
+
+ @Override
+ protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model) {
+ context.addStep(new AbstractDeploymentChainStep() {
+ @Override
+ protected void execute(DeploymentProcessorTarget processorTarget) {
+ 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
+ chooseConfigDeploymentProcessor());
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorWildFly();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java
new file mode 100755
index 0000000..76ca0bc
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemDefinition.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.subsystem.adapter.saml.extension;
+
+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;
+
+/**
+ * Definition of subsystem=keycloak-saml.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
+
+ static final KeycloakSubsystemDefinition INSTANCE = new KeycloakSubsystemDefinition();
+
+ private KeycloakSubsystemDefinition() {
+ super(KeycloakSamlExtension.SUBSYSTEM_PATH,
+ KeycloakSamlExtension.getResourceDescriptionResolver("subsystem"),
+ KeycloakSubsystemAdd.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE
+ );
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+}
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeycloakSubsystemParser.java
new file mode 100755
index 0000000..8b20565
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyDefinition.java
new file mode 100644
index 0000000..7d19994
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreCertificateDefinition.java
new file mode 100644
index 0000000..83dc057
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStoreDefinition.java
new file mode 100644
index 0000000..a71e89a
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/KeyStorePrivateKeyDefinition.java
new file mode 100644
index 0000000..bbb398d
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakLogger.java b/adapters/saml/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/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/logging/KeycloakMessages.java b/adapters/saml/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/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentAddHandler.java
new file mode 100644
index 0000000..22de93d
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SecureDeploymentDefinition.java
new file mode 100644
index 0000000..bf6cab5
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderAddHandler.java
new file mode 100644
index 0000000..f9f3070
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/ServiceProviderDefinition.java
new file mode 100644
index 0000000..cb84f12
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleLogoutDefinition.java
new file mode 100644
index 0000000..6ad99b1
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java b/adapters/saml/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/saml/extension/SingleSignOnDefinition.java
new file mode 100644
index 0000000..fe78c02
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100755
index 0000000..25b4bb8
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.adapter.saml.extension.KeycloakSamlExtension
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/org/keycloak/subsystem/adapter/saml/extension/LocalDescriptions.properties
new file mode 100755
index 0000000..f8a4a11
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/schema/wildfly-keycloak-saml_1_1.xsd
new file mode 100755
index 0000000..725104b
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml
new file mode 100755
index 0000000..9696cbd
--- /dev/null
+++ b/adapters/saml/wildfly/wildfly-subsystem/src/main/resources/subsystem-templates/keycloak-saml-adapter.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Template used by WildFly build when directed to include Keycloak SAML subsystem in a configuration. -->
+<config>
+ <extension-module>org.keycloak.keycloak-saml-adapter-subsystem</extension-module>
+ <subsystem xmlns="urn:jboss:domain:keycloak-saml:1.1">
+ </subsystem>
+</config>
diff --git a/adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java b/adapters/saml/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/saml/extension/SubsystemParsingTestCase.java
new file mode 100755
index 0000000..fdd8ee2
--- /dev/null
+++ b/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1.xml b/adapters/saml/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/adapters/saml/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/adapters/saml/wildfly/wildfly-subsystem/src/test/resources/org/keycloak/subsystem/adapter/saml/extension/keycloak-saml-1.1-err.xml b/adapters/saml/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/adapters/saml/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
adapters/spi/pom.xml 23(+23 -0)
diff --git a/adapters/spi/pom.xml b/adapters/spi/pom.xml
new file mode 100755
index 0000000..f387e86
--- /dev/null
+++ b/adapters/spi/pom.xml
@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak Client Adapter SPI Modules</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-client-adapter-spi-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>adapter-spi</module>
+ <module>tomcat-adapter-spi</module>
+ <module>undertow-adapter-spi</module>
+ <module>servlet-adapter-spi</module>
+ <module>jboss-adapter-core</module>
+ </modules>
+</project>
adapters/spi/tomcat-adapter-spi/pom.xml 69(+69 -0)
diff --git a/adapters/spi/tomcat-adapter-spi/pom.xml b/adapters/spi/tomcat-adapter-spi/pom.xml
new file mode 100755
index 0000000..44d404d
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.9.0.CR1-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-tomcat-adapter-spi</artifactId>
+ <name>Keycloak Tomcat Adapter SPI</name>
+ <properties>
+ <!-- <tomcat.version>8.0.14</tomcat.version> -->
+ <!-- <tomcat.version>7.0.52</tomcat.version> -->
+ <tomcat.version>6.0.41</tomcat.version>
+ </properties>
+ <description />
+
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging</artifactId>
+ <version>${jboss.logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-common</artifactId>
+ </dependency>
+ <!--
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>tomcat-servlet-api</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ -->
+ <dependency>
+ <groupId>org.apache.tomcat</groupId>
+ <artifactId>catalina</artifactId>
+ <version>${tomcat.version}</version>
+ <scope>compile</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java
new file mode 100755
index 0000000..2520f70
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagement.java
@@ -0,0 +1,77 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.catalina.SessionEvent;
+import org.apache.catalina.SessionListener;
+import org.apache.catalina.realm.GenericPrincipal;
+import org.jboss.logging.Logger;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Manages relationship to users and sessions so that forced admin logout can be implemented
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class CatalinaUserSessionManagement implements SessionListener {
+ private static final Logger log = Logger.getLogger(CatalinaUserSessionManagement.class);
+
+ public void login(Session session) {
+ session.addSessionListener(this);
+ }
+
+ public void logoutAll(Manager sessionManager) {
+ Session[] allSessions = sessionManager.findSessions();
+ for (Session session : allSessions) {
+ logoutSession(session);
+ }
+ }
+
+ public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
+ log.debug("logoutHttpSessions: " + sessionIds);
+
+ for (String sessionId : sessionIds) {
+ logoutSession(sessionManager, sessionId);
+ }
+ }
+
+ protected void logoutSession(Manager manager, String httpSessionId) {
+ log.debug("logoutHttpSession: " + httpSessionId);
+
+ Session session;
+ try {
+ session = manager.findSession(httpSessionId);
+ } catch (IOException ioe) {
+ log.warn("IO exception when looking for session " + httpSessionId, ioe);
+ return;
+ }
+
+ logoutSession(session);
+ }
+
+ protected void logoutSession(Session session) {
+ try {
+ session.expire();
+ } catch (Exception e) {
+ log.warn("Session not present or already invalidated.", e);
+ }
+ }
+
+ public void sessionEvent(SessionEvent event) {
+ // We only care about session destroyed events
+ if (!Session.SESSION_DESTROYED_EVENT.equals(event.getType()))
+ return;
+
+ // Look up the single session id associated with this session (if any)
+ Session session = event.getSession();
+ log.debugf("Session %s destroyed", session.getId());
+
+ GenericPrincipal principal = (GenericPrincipal) session.getPrincipal();
+ if (principal == null) return;
+ session.setPrincipal(null);
+ session.setAuthType(null);
+ }
+}
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java
new file mode 100755
index 0000000..53982d2
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/CatalinaUserSessionManagementWrapper.java
@@ -0,0 +1,30 @@
+package org.keycloak.adapters.tomcat;
+
+import java.util.List;
+
+import org.apache.catalina.Manager;
+import org.keycloak.adapters.spi.UserSessionManagement;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CatalinaUserSessionManagementWrapper implements UserSessionManagement {
+
+ private final CatalinaUserSessionManagement delegate;
+ private final Manager sessionManager;
+
+ public CatalinaUserSessionManagementWrapper(CatalinaUserSessionManagement delegate, Manager sessionManager) {
+ this.delegate = delegate;
+ this.sessionManager = sessionManager;
+ }
+
+ @Override
+ public void logoutAll() {
+ delegate.logoutAll(sessionManager);
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ delegate.logoutHttpSessions(sessionManager, ids);
+ }
+}
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/GenericPrincipalFactory.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/GenericPrincipalFactory.java
new file mode 100755
index 0000000..201a409
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/GenericPrincipalFactory.java
@@ -0,0 +1,109 @@
+package org.keycloak.adapters.tomcat;
+
+import org.apache.catalina.Realm;
+import org.apache.catalina.realm.GenericPrincipal;
+
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:ungarida@gmail.com">Davide Ungari</a>
+ * @version $Revision: 1 $
+ */
+public abstract class GenericPrincipalFactory {
+
+ public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet) {
+ Subject subject = new Subject();
+ Set<Principal> principals = subject.getPrincipals();
+ principals.add(identity);
+ Group[] roleSets = getRoleSets(roleSet);
+ for (int g = 0; g < roleSets.length; g++) {
+ Group group = roleSets[g];
+ String name = group.getName();
+ Group subjectGroup = createGroup(name, principals);
+ // Copy the group members to the Subject group
+ Enumeration<? extends Principal> members = group.members();
+ while (members.hasMoreElements()) {
+ Principal role = (Principal) members.nextElement();
+ subjectGroup.addMember(role);
+ }
+ }
+
+ Principal userPrincipal = getPrincipal(subject);
+ List<String> rolesAsStringList = new ArrayList<String>();
+ rolesAsStringList.addAll(roleSet);
+ GenericPrincipal principal = createPrincipal(userPrincipal, rolesAsStringList);
+ return principal;
+ }
+
+ protected abstract GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles);
+
+ /**
+ * Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
+ * considered or the single subject inside the CallerPrincipal group.
+ *
+ * @param subject
+ * @return the authenticated subject
+ */
+ protected Principal getPrincipal(Subject subject) {
+ Principal principal = null;
+ Principal callerPrincipal = null;
+ if (subject != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ if (principals != null && !principals.isEmpty()) {
+ for (Principal p : principals) {
+ if (!(p instanceof Group) && principal == null) {
+ principal = p;
+ }
+// if (p instanceof Group) {
+// Group g = Group.class.cast(p);
+// if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
+// Enumeration<? extends Principal> e = g.members();
+// if (e.hasMoreElements())
+// callerPrincipal = e.nextElement();
+// }
+// }
+ }
+ }
+ }
+ return callerPrincipal == null ? principal : callerPrincipal;
+ }
+
+ protected Group createGroup(String name, Set<Principal> principals) {
+ Group roles = null;
+ Iterator<Principal> iter = principals.iterator();
+ while (iter.hasNext()) {
+ Object next = iter.next();
+ if ((next instanceof Group) == false)
+ continue;
+ Group grp = (Group) next;
+ if (grp.getName().equals(name)) {
+ roles = grp;
+ break;
+ }
+ }
+ // If we did not find a group create one
+ if (roles == null) {
+ roles = new SimpleGroup(name);
+ principals.add(roles);
+ }
+ return roles;
+ }
+
+ protected Group[] getRoleSets(Collection<String> roleSet) {
+ SimpleGroup roles = new SimpleGroup("Roles");
+ Group[] roleSets = {roles};
+ for (String role : roleSet) {
+ roles.addMember(new SimplePrincipal(role));
+ }
+ return roleSets;
+ }
+
+}
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java
new file mode 100755
index 0000000..109ffe1
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimpleGroup.java
@@ -0,0 +1,41 @@
+package org.keycloak.adapters.tomcat;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SimpleGroup extends SimplePrincipal implements Group {
+ private final Set<Principal> members = new HashSet<Principal>();
+
+ /**
+ * Creates a new group with the given name.
+ * @param name Group name.
+ */
+ public SimpleGroup(final String name) {
+ super(name);
+ }
+
+ public boolean addMember(final Principal user) {
+ return this.members.add(user);
+ }
+
+ public boolean isMember(final Principal member) {
+ return this.members.contains(member);
+ }
+
+ public Enumeration<? extends Principal> members() {
+ return Collections.enumeration(this.members);
+ }
+
+ public boolean removeMember(final Principal user) {
+ return this.members.remove(user);
+ }
+
+ public String toString() {
+ return super.toString() + ": " + members.toString();
+ }
+
+}
diff --git a/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java
new file mode 100755
index 0000000..29f46ec
--- /dev/null
+++ b/adapters/spi/tomcat-adapter-spi/src/main/java/org/keycloak/adapters/tomcat/SimplePrincipal.java
@@ -0,0 +1,50 @@
+package org.keycloak.adapters.tomcat;
+
+import java.io.Serializable;
+import java.security.Principal;
+
+/**
+ * Simple security principal implementation.
+ *
+ * @author Marvin S. Addison
+ * @version $Revision: 22071 $
+ * @since 3.1.11
+ *
+ */
+public class SimplePrincipal implements Principal, Serializable {
+
+ /** SimplePrincipal.java */
+
+ /** The unique identifier for this principal. */
+ private final String name;
+
+ /**
+ * Creates a new principal with the given name.
+ * @param name Principal name.
+ */
+ public SimplePrincipal(final String name) {
+ this.name = name;
+ }
+
+ public final String getName() {
+ return this.name;
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ public boolean equals(final Object o) {
+ if (o == null) {
+ return false;
+ } else if (!(o instanceof SimplePrincipal)) {
+ return false;
+ } else {
+ return getName().equals(((SimplePrincipal)o).getName());
+ }
+ }
+
+ public int hashCode() {
+ return 37 * getName().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
new file mode 100755
index 0000000..815b1ce
--- /dev/null
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/ServletHttpFacade.java
@@ -0,0 +1,81 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.servlet.handlers.ServletRequestContext;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ServletHttpFacade extends UndertowHttpFacade {
+ protected HttpServletRequest request;
+ protected HttpServletResponse response;
+
+ public ServletHttpFacade(HttpServerExchange exchange) {
+ super(exchange);
+ final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
+ request = (HttpServletRequest)servletRequestContext.getServletRequest();
+ response = (HttpServletResponse)servletRequestContext.getServletResponse();
+ }
+
+ protected class RequestFacade extends UndertowHttpFacade.RequestFacade {
+ @Override
+ public String getFirstParam(String param) {
+ return request.getParameter(param);
+ }
+
+ @Override
+ public void setError(AuthenticationError error) {
+ request.setAttribute(AuthenticationError.class.getName(), error);
+
+ }
+
+ @Override
+ public void setError(LogoutError error) {
+ request.setAttribute(LogoutError.class.getName(), error);
+ }
+
+
+ }
+
+ protected class ResponseFacade extends UndertowHttpFacade.ResponseFacade {
+ // can't call sendError from a challenge. Undertow ends up calling send error.
+ /*
+ @Override
+ public void sendError(int code) {
+ try {
+ response.sendError(code);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void sendError(int code, String message) {
+ try {
+ response.sendError(code, message);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ */
+
+ }
+
+ @Override
+ public Response getResponse() {
+ return new ResponseFacade();
+ }
+
+ @Override
+ public Request getRequest() {
+ return new RequestFacade();
+ }
+}
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
new file mode 100755
index 0000000..f9ac9ae
--- /dev/null
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/SessionManagementBridge.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.session.SessionManager;
+import org.keycloak.adapters.spi.UserSessionManagement;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SessionManagementBridge implements UserSessionManagement {
+
+ protected UndertowUserSessionManagement userSessionManagement;
+ protected SessionManager sessionManager;
+
+ public SessionManagementBridge(UndertowUserSessionManagement userSessionManagement, SessionManager sessionManager) {
+ this.userSessionManagement = userSessionManagement;
+ this.sessionManager = sessionManager;
+ }
+
+ @Override
+ public void logoutAll() {
+ userSessionManagement.logoutAll(sessionManager);
+ }
+
+ @Override
+ public void logoutHttpSessions(List<String> ids) {
+ userSessionManagement.logoutHttpSessions(sessionManager, ids);
+ }
+}
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
new file mode 100755
index 0000000..d38fe55
--- /dev/null
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowHttpFacade.java
@@ -0,0 +1,213 @@
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.handlers.CookieImpl;
+import io.undertow.util.AttachmentKey;
+import io.undertow.util.Headers;
+import io.undertow.util.HttpString;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.common.util.KeycloakUriBuilder;
+
+import javax.security.cert.X509Certificate;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowHttpFacade implements HttpFacade {
+ public static final AttachmentKey<AuthenticationError> AUTH_ERROR_ATTACHMENT_KEY = AttachmentKey.create(AuthenticationError.class);
+ public static final AttachmentKey<LogoutError> LOGOUT_ERROR_ATTACHMENT_KEY = AttachmentKey.create(LogoutError.class);
+
+ protected HttpServerExchange exchange;
+ protected RequestFacade requestFacade = new RequestFacade();
+ protected ResponseFacade responseFacade = new ResponseFacade();
+
+ public UndertowHttpFacade(HttpServerExchange exchange) {
+ this.exchange = exchange;
+ }
+
+ @Override
+ public Request getRequest() {
+ return requestFacade;
+ }
+
+ @Override
+ public Response getResponse() {
+ return responseFacade;
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain() {
+ X509Certificate[] chain = new X509Certificate[0];
+ try {
+ chain = exchange.getConnection().getSslSessionInfo().getPeerCertificateChain();
+ } catch (Exception ignore) {
+
+ }
+ return chain;
+ }
+
+ protected class RequestFacade implements Request {
+ @Override
+ public String getURI() {
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
+ .replaceQuery(exchange.getQueryString());
+ if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
+ return uriBuilder.build().toString();
+ }
+
+ @Override
+ public boolean isSecure() {
+ String protocol = exchange.getRequestScheme();
+ return protocol.equalsIgnoreCase("https");
+ }
+
+ @Override
+ public String getFirstParam(String param) {
+ throw new RuntimeException("Not implemented yet");
+ }
+
+ @Override
+ public String getQueryParamValue(String param) {
+ Map<String,Deque<String>> queryParameters = exchange.getQueryParameters();
+ if (queryParameters == null) return null;
+ Deque<String> strings = queryParameters.get(param);
+ if (strings == null) return null;
+ return strings.getFirst();
+ }
+
+ @Override
+ public Cookie getCookie(String cookieName) {
+ Map<String, io.undertow.server.handlers.Cookie> requestCookies = exchange.getRequestCookies();
+ if (requestCookies == null) return null;
+ io.undertow.server.handlers.Cookie cookie = requestCookies.get(cookieName);
+ if (cookie == null) return null;
+ return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
+ }
+
+ @Override
+ public List<String> getHeaders(String name) {
+ return exchange.getRequestHeaders().get(name);
+ }
+
+ @Override
+ public String getMethod() {
+ return exchange.getRequestMethod().toString();
+ }
+
+
+
+ @Override
+ public String getHeader(String name) {
+ return exchange.getRequestHeaders().getFirst(name);
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ if (!exchange.isBlocking()) exchange.startBlocking();
+ return exchange.getInputStream();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ InetSocketAddress sourceAddress = exchange.getSourceAddress();
+ if (sourceAddress == null) {
+ return "";
+ }
+ InetAddress address = sourceAddress.getAddress();
+ if (address == null) {
+ // this is unresolved, so we just return the host name not exactly spec, but if the name should be
+ // resolved then a PeerNameResolvingHandler should be used and this is probably better than just
+ // returning null
+ return sourceAddress.getHostString();
+ }
+ return address.getHostAddress();
+ }
+
+ @Override
+ public void setError(AuthenticationError error) {
+ exchange.putAttachment(AUTH_ERROR_ATTACHMENT_KEY, error);
+ }
+
+ @Override
+ public void setError(LogoutError error) {
+ exchange.putAttachment(LOGOUT_ERROR_ATTACHMENT_KEY, error);
+
+ }
+ }
+
+ protected class ResponseFacade implements Response {
+ @Override
+ public void setStatus(int status) {
+ exchange.setResponseCode(status);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+ exchange.getResponseHeaders().add(new HttpString(name), value);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ exchange.getResponseHeaders().put(new HttpString(name), value);
+ }
+
+ @Override
+ public void resetCookie(String name, String path) {
+ CookieImpl cookie = new CookieImpl(name, "");
+ cookie.setMaxAge(0);
+ cookie.setPath(path);
+ exchange.setResponseCookie(cookie);
+ }
+
+ @Override
+ public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
+ CookieImpl cookie = new CookieImpl(name, value);
+ cookie.setPath(path);
+ cookie.setDomain(domain);
+ cookie.setMaxAge(maxAge);
+ cookie.setSecure(secure);
+ cookie.setHttpOnly(httpOnly);
+ exchange.setResponseCookie(cookie);
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ if (!exchange.isBlocking()) exchange.startBlocking();
+ return exchange.getOutputStream();
+ }
+
+ @Override
+ public void sendError(int code) {
+ exchange.setResponseCode(code);
+ }
+
+ @Override
+ public void sendError(int code, String message) {
+ exchange.setResponseCode(code);
+ exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html");
+ try {
+ exchange.getOutputStream().write(message.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ exchange.endExchange();
+ }
+
+
+ @Override
+ public void end() {
+ exchange.endExchange();
+ }
+ }
+}
diff --git a/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
new file mode 100755
index 0000000..0b9a9a2
--- /dev/null
+++ b/adapters/spi/undertow-adapter-spi/src/main/java/org/keycloak/adapters/undertow/UndertowUserSessionManagement.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.keycloak.adapters.undertow;
+
+import io.undertow.server.HttpServerExchange;
+import io.undertow.server.session.Session;
+import io.undertow.server.session.SessionConfig;
+import io.undertow.server.session.SessionListener;
+import io.undertow.server.session.SessionManager;
+import org.jboss.logging.Logger;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manages relationship to users and sessions so that forced admin logout can be implemented
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UndertowUserSessionManagement implements SessionListener {
+ private static final Logger log = Logger.getLogger(UndertowUserSessionManagement.class);
+ protected volatile boolean registered;
+
+ public void login(SessionManager manager) {
+ if (!registered) {
+ manager.registerSessionListener(this);
+ registered = true;
+ }
+ }
+
+ public void logoutAll(SessionManager manager) {
+ Set<String> allSessions = manager.getAllSessions();
+ for (String sessionId : allSessions) logoutSession(manager, sessionId);
+ }
+
+ public void logoutHttpSessions(SessionManager manager, List<String> sessionIds) {
+ log.debug("logoutHttpSessions: " + sessionIds);
+
+ for (String sessionId : sessionIds) {
+ logoutSession(manager, sessionId);
+ }
+ }
+
+ protected void logoutSession(SessionManager manager, String httpSessionId) {
+ log.debug("logoutHttpSession: " + httpSessionId);
+ Session session = getSessionById(manager, httpSessionId);
+ try {
+ if (session != null) session.invalidate(null);
+ } catch (Exception e) {
+ log.warnf("Session %s not present or already invalidated.", httpSessionId);
+ }
+ }
+
+ protected Session getSessionById(SessionManager manager, final String sessionId) {
+ // TODO: Workaround for WFLY-3345. Remove this once we move to wildfly 8.2
+ if (manager.getClass().getName().equals("org.wildfly.clustering.web.undertow.session.DistributableSessionManager")) {
+ return manager.getSession(null, new SessionConfig() {
+
+ @Override
+ public void setSessionId(HttpServerExchange exchange, String sessionId) {
+ }
+
+ @Override
+ public void clearSession(HttpServerExchange exchange, String sessionId) {
+ }
+
+ @Override
+ public String findSessionId(HttpServerExchange exchange) {
+ return sessionId;
+ }
+
+ @Override
+ public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) {
+ return null;
+ }
+
+ @Override
+ public String rewriteUrl(String originalUrl, String sessionId) {
+ return null;
+ }
+
+ });
+
+ } else {
+ return manager.getSession(sessionId);
+ }
+ }
+
+
+ @Override
+ public void sessionCreated(Session session, HttpServerExchange exchange) {
+ }
+
+ @Override
+ public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) {
+ }
+
+
+ @Override
+ public void sessionIdChanged(Session session, String oldSessionId) {
+ }
+
+ @Override
+ public void attributeAdded(Session session, String name, Object value) {
+ }
+
+ @Override
+ public void attributeUpdated(Session session, String name, Object newValue, Object oldValue) {
+ }
+
+ @Override
+ public void attributeRemoved(Session session, String name, Object oldValue) {
+ }
+
+}
dependencies/server-all/pom.xml 38(+0 -38)
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index f2f0ac3..455ea46 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -30,42 +30,9 @@
</dependency>
<!-- identity providers -->
<dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-broker-oidc</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-broker-saml</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-github</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-google</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-twitter</artifactId>
- </dependency>
- <dependency>
<groupId>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
</dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-facebook</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-linkedin</artifactId>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-stackoverflow</artifactId>
- </dependency>
-
<!-- ldap federation api -->
<dependency>
<groupId>org.keycloak</groupId>
@@ -81,11 +48,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core</artifactId>
</dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-protocol</artifactId>
- </dependency>
-
<!-- mongo -->
<dependency>
<groupId>org.keycloak</groupId>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
index 07cdcf2..eb63999 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml
@@ -11,14 +11,7 @@
<module name="org.keycloak.keycloak-model-jpa" services="import" meta-inf="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-infinispan" services="import"/>
- <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
- <module name="org.keycloak.keycloak-social-facebook" services="import"/>
- <module name="org.keycloak.keycloak-social-github" services="import"/>
- <module name="org.keycloak.keycloak-social-google" services="import"/>
- <module name="org.keycloak.keycloak-social-twitter" services="import"/>
- <module name="org.keycloak.keycloak-social-linkedin" services="import"/>
- <module name="org.keycloak.keycloak-social-stackoverflow" services="import"/>
<module name="org.hibernate" services="import"/>
<module name="org.bouncycastle"/>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
index 46916be..267e926 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml
@@ -7,8 +7,6 @@
<artifact name="${org.keycloak:keycloak-services}"/>
</resources>
<dependencies>
- <module name="org.keycloak.keycloak-broker-oidc" services="import"/>
- <module name="org.keycloak.keycloak-broker-saml" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-forms-common-themes" services="import"/>
@@ -20,14 +18,7 @@
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/>
- <module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
- <module name="org.keycloak.keycloak-social-facebook" services="import"/>
- <module name="org.keycloak.keycloak-social-github" services="import"/>
- <module name="org.keycloak.keycloak-social-google" services="import"/>
- <module name="org.keycloak.keycloak-social-twitter" services="import"/>
- <module name="org.keycloak.keycloak-social-linkedin" services="import"/>
- <module name="org.keycloak.keycloak-social-stackoverflow" services="import"/>
<module name="org.keycloak.keycloak-wildfly-extensions" services="import"/>
<module name="org.freemarker"/>
@@ -48,6 +39,7 @@
<module name="javax.api"/>
<module name="javax.activation.api"/>
<module name="org.apache.httpcomponents"/>
+ <module name="org.twitter4j"/>
</dependencies>
</module>
forms/common-themes/pom.xml 30(+0 -30)
diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml
index a3e47cc..1dac77b 100755
--- a/forms/common-themes/pom.xml
+++ b/forms/common-themes/pom.xml
@@ -14,36 +14,6 @@
<description />
<dependencies>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-core</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-server-spi</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.jboss.spec.javax.ws.rs</groupId>
- <artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jaxrs</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>org.jboss.logging</groupId>
- <artifactId>jboss-logging</artifactId>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>com.fasterxml.jackson.core</groupId>
- <artifactId>jackson-core</artifactId>
- <scope>provided</scope>
- </dependency>
</dependencies>
<build>
integration/pom.xml 18(+0 -18)
diff --git a/integration/pom.xml b/integration/pom.xml
index 69767e6..0b4145c 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -14,24 +14,6 @@
<packaging>pom</packaging>
<modules>
- <module>adapter-spi</module>
- <module>servlet-adapter-spi</module>
- <module>adapter-core</module>
- <module>jaxrs-oauth-client</module>
- <module>servlet-oauth-client</module>
- <module>jboss-adapter-core</module>
- <module>tomcat</module>
- <module>as7-eap6</module>
- <module>jetty</module>
- <module>undertow-adapter-spi</module>
- <module>undertow</module>
- <module>wildfly</module>
- <module>servlet-filter</module>
- <module>js</module>
- <module>installed</module>
<module>admin-client</module>
- <module>osgi-adapter</module>
- <module>spring-boot</module>
- <module>spring-security</module>
</modules>
</project>
pom.xml 54(+4 -50)
diff --git a/pom.xml b/pom.xml
index f200a2a..9c7e887 100755
--- a/pom.xml
+++ b/pom.xml
@@ -142,18 +142,17 @@
<module>client-registration</module>
<module>dependencies</module>
<module>server-spi</module>
- <module>integration</module>
+ <module>saml</module>
<module>proxy</module>
<module>federation</module>
<module>services</module>
+ <module>forms</module>
<module>model</module>
<module>events</module>
- <module>saml</module>
- <module>broker</module>
- <module>social</module>
- <module>forms</module>
<module>util</module>
<module>wildfly</module>
+ <module>integration</module>
+ <module>adapters</module>
<module>examples</module>
<module>testsuite</module>
</modules>
@@ -606,16 +605,6 @@
<!-- keycloak -->
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-broker-oidc</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-broker-saml</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-client-registration-api</artifactId>
<version>${project.version}</version>
</dependency>
@@ -903,11 +892,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-saml-protocol</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-core</artifactId>
<version>${project.version}</version>
</dependency>
@@ -958,36 +942,6 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-facebook</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-github</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-google</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-linkedin</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-stackoverflow</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
- <artifactId>keycloak-social-twitter</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-as7-modules</artifactId>
<version>${project.version}</version>
<type>zip</type>
saml/pom.xml 2(+0 -2)
diff --git a/saml/pom.xml b/saml/pom.xml
index 6f3a226..a5ce390 100755
--- a/saml/pom.xml
+++ b/saml/pom.xml
@@ -15,7 +15,5 @@
<modules>
<module>saml-core</module>
- <module>saml-protocol</module>
- <module>client-adapter</module>
</modules>
</project>
services/pom.xml 9(+9 -0)
diff --git a/services/pom.xml b/services/pom.xml
index fa206c9..3f7dba9 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -48,6 +48,10 @@
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.twitter4j</groupId>
+ <artifactId>twitter4j-core</artifactId>
+ </dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
@@ -96,6 +100,11 @@
<artifactId>javase</artifactId>
</dependency>
<dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-saml-core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
diff --git a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
new file mode 100755
index 0000000..1ee3cd2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java
@@ -0,0 +1,35 @@
+package org.keycloak.protocol.saml.clientregistration;
+
+import org.keycloak.exportimport.ClientDescriptionConverter;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class EntityDescriptorClientRegistrationProvider extends AbstractClientRegistrationProvider {
+
+ public EntityDescriptorClientRegistrationProvider(KeycloakSession session) {
+ super(session);
+ }
+
+ @POST
+ @Consumes(MediaType.APPLICATION_XML)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response createSaml(String descriptor) {
+ ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor);
+ client = create(client);
+ URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
+ return Response.created(uri).entity(client).build();
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java
new file mode 100644
index 0000000..393ff36
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java
@@ -0,0 +1,38 @@
+package org.keycloak.protocol.saml.clientregistration;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.services.clientregistration.ClientRegistrationProvider;
+import org.keycloak.services.clientregistration.ClientRegistrationProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class EntityDescriptorClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
+
+ public static final String ID = "saml2-entity-descriptor";
+
+ @Override
+ public ClientRegistrationProvider create(KeycloakSession session) {
+ return new EntityDescriptorClientRegistrationProvider(session);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
new file mode 100755
index 0000000..61ecbdc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java
@@ -0,0 +1,167 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.Config;
+import org.keycloak.dom.saml.v2.metadata.*;
+import org.keycloak.exportimport.ClientDescriptionConverter;
+import org.keycloak.exportimport.ClientDescriptionConverterFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ParsingException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
+import org.keycloak.saml.processing.core.saml.v2.util.SAMLMetadataUtil;
+import org.keycloak.saml.processing.core.util.CoreConfigUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
+
+ public static final String ID = "saml2-entity-descriptor";
+
+ @Override
+ public boolean isSupported(String description) {
+ description = description.trim();
+ return (description.startsWith("<") && description.endsWith(">") && description.contains("EntityDescriptor"));
+ }
+
+ @Override
+ public ClientRepresentation convertToInternal(String description) {
+ return loadEntityDescriptors(new ByteArrayInputStream(description.getBytes()));
+ }
+
+ private static ClientRepresentation loadEntityDescriptors(InputStream is) {
+ Object metadata;
+ try {
+ metadata = new SAMLParser().parse(is);
+ } catch (ParsingException e) {
+ throw new RuntimeException(e);
+ }
+ EntitiesDescriptorType entities;
+
+ if (EntitiesDescriptorType.class.isInstance(metadata)) {
+ entities = (EntitiesDescriptorType) metadata;
+ } else {
+ entities = new EntitiesDescriptorType();
+ entities.addEntityDescriptor(metadata);
+ }
+
+ if (entities.getEntityDescriptor().size() != 1) {
+ throw new RuntimeException("Expected one entity descriptor");
+ }
+
+ EntityDescriptorType entity = (EntityDescriptorType) entities.getEntityDescriptor().get(0);
+ String entityId = entity.getEntityID();
+
+ ClientRepresentation app = new ClientRepresentation();
+ app.setClientId(entityId);
+
+ Map<String, String> attributes = new HashMap<>();
+ app.setAttributes(attributes);
+
+ List<String> redirectUris = new LinkedList<>();
+ app.setRedirectUris(redirectUris);
+
+ app.setFullScopeAllowed(true);
+ app.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ attributes.put(SamlConfigAttributes.SAML_SERVER_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE); // default to true
+ attributes.put(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, SignatureAlgorithm.RSA_SHA256.toString());
+ attributes.put(SamlConfigAttributes.SAML_AUTHNSTATEMENT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
+ SPSSODescriptorType spDescriptorType = CoreConfigUtil.getSPDescriptor(entity);
+ if (spDescriptorType.isWantAssertionsSigned()) {
+ attributes.put(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
+ }
+ String logoutPost = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
+ if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE, logoutPost);
+ String logoutRedirect = getLogoutLocation(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
+ if (logoutPost != null) attributes.put(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_REDIRECT_ATTRIBUTE, logoutRedirect);
+
+ String assertionConsumerServicePostBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get());
+ if (assertionConsumerServicePostBinding != null) {
+ attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE, assertionConsumerServicePostBinding);
+ redirectUris.add(assertionConsumerServicePostBinding);
+ }
+ String assertionConsumerServiceRedirectBinding = CoreConfigUtil.getServiceURL(spDescriptorType, JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get());
+ if (assertionConsumerServiceRedirectBinding != null) {
+ attributes.put(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE, assertionConsumerServiceRedirectBinding);
+ redirectUris.add(assertionConsumerServiceRedirectBinding);
+ }
+
+ for (KeyDescriptorType keyDescriptor : spDescriptorType.getKeyDescriptor()) {
+ X509Certificate cert = null;
+ try {
+ cert = SAMLMetadataUtil.getCertificate(keyDescriptor);
+ } catch (ConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ String certPem = KeycloakModelUtils.getPemFromCertificate(cert);
+ if (keyDescriptor.getUse() == KeyTypes.SIGNING) {
+ attributes.put(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
+ attributes.put(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, certPem);
+ } else if (keyDescriptor.getUse() == KeyTypes.ENCRYPTION) {
+ attributes.put(SamlConfigAttributes.SAML_ENCRYPT, SamlProtocol.ATTRIBUTE_TRUE_VALUE);
+ attributes.put(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, certPem);
+ }
+ }
+
+ return app;
+ }
+
+ private static String getLogoutLocation(SPSSODescriptorType idp, String bindingURI) {
+ String logoutResponseLocation = null;
+
+ List<EndpointType> endpoints = idp.getSingleLogoutService();
+ for (EndpointType endpoint : endpoints) {
+ if (endpoint.getBinding().toString().equals(bindingURI)) {
+ if (endpoint.getLocation() != null) {
+ logoutResponseLocation = endpoint.getLocation().toString();
+ } else {
+ logoutResponseLocation = null;
+ }
+
+ break;
+ }
+
+ }
+ return logoutResponseLocation;
+ }
+
+ @Override
+ public ClientDescriptionConverter create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
new file mode 100755
index 0000000..1fb742e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/KeycloakSamlClientInstallation.java
@@ -0,0 +1,160 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.services.resources.RealmsResource;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class KeycloakSamlClientInstallation implements ClientInstallationProvider {
+
+ @Override
+ public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI baseUri) {
+ SamlClient samlClient = new SamlClient(client);
+ StringBuffer buffer = new StringBuffer();
+ buffer.append("<keycloak-saml-adapter>\n");
+ buffer.append(" <SP entityID=\"").append(client.getClientId()).append("\"\n");
+ buffer.append(" sslPolicy=\"").append(realm.getSslRequired().name()).append("\"\n");
+ buffer.append(" logoutPage=\"SPECIFY YOUR LOGOUT PAGE!\">\n");
+ if (samlClient.requiresClientSignature() || samlClient.requiresEncryption()) {
+ buffer.append(" <Keys>\n");
+ if (samlClient.requiresClientSignature()) {
+ buffer.append(" <Key signing=\"true\">\n");
+ buffer.append(" <PrivateKeyPem>\n");
+ if (samlClient.getClientSigningPrivateKey() == null) {
+ buffer.append(" PRIVATE KEY NOT SET UP OR KNOWN\n");
+ } else {
+ buffer.append(" ").append(samlClient.getClientSigningPrivateKey()).append("\n");
+ }
+ buffer.append(" </PrivateKeyPem>\n");
+ buffer.append(" <CertificatePem>\n");
+ if (samlClient.getClientSigningCertificate() == null) {
+ buffer.append(" YOU MUST CONFIGURE YOUR CLIENT's SIGNING CERTIFICATE\n");
+ } else {
+ buffer.append(" ").append(samlClient.getClientSigningCertificate()).append("\n");
+ }
+ buffer.append(" </CertificatePem>\n");
+ buffer.append(" </Key>\n");
+ }
+ if (samlClient.requiresEncryption()) {
+ buffer.append(" <Key encryption=\"true\">\n");
+ buffer.append(" <PrivateKeyPem>\n");
+ if (samlClient.getClientEncryptingPrivateKey() == null) {
+ buffer.append(" PRIVATE KEY NOT SET UP OR KNOWN\n");
+ } else {
+ buffer.append(" ").append(samlClient.getClientEncryptingPrivateKey()).append("\n");
+ }
+ buffer.append(" </PrivateKeyPem>\n");
+ buffer.append(" </Key>\n");
+
+ }
+ buffer.append(" </Keys>\n");
+ }
+ buffer.append(" <IDP entityID=\"idp\"");
+ if (samlClient.requiresClientSignature()) {
+ buffer.append("\n signatureAlgorithm=\"").append(samlClient.getSignatureAlgorithm()).append("\"");
+ if (samlClient.getCanonicalizationMethod() != null) {
+ buffer.append("\n signatureCanonicalizationMethod=\"").append(samlClient.getCanonicalizationMethod()).append("\"");
+ }
+ }
+ buffer.append(">\n");
+ buffer.append(" <SingleSignOnService signRequest=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
+ buffer.append(" validateResponseSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
+ buffer.append(" requestBinding=\"POST\"\n");
+ UriBuilder bindingUrlBuilder = UriBuilder.fromUri(baseUri);
+ String bindingUrl = RealmsResource.protocolUrl(bindingUrlBuilder)
+ .build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
+ buffer.append(" bindingUrl=\"").append(bindingUrl).append("\"/>\n");
+
+ buffer.append(" <SingleLogoutService signRequest=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
+ buffer.append(" signResponse=\"").append(Boolean.toString(samlClient.requiresClientSignature())).append("\"\n");
+ buffer.append(" validateRequestSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
+ buffer.append(" validateResponseSignature=\"").append(Boolean.toString(samlClient.requiresRealmSignature())).append("\"\n");
+ buffer.append(" requestBinding=\"POST\"\n");
+ buffer.append(" responseBinding=\"POST\"\n");
+ buffer.append(" postBindingUrl=\"").append(bindingUrl).append("\"\n");
+ buffer.append(" redirectBindingUrl=\"").append(bindingUrl).append("\"");
+ buffer.append("/>\n");
+ if (samlClient.requiresRealmSignature()) {
+ buffer.append(" <Keys>\n");
+ buffer.append(" <Key signing=\"true\">\n");
+ buffer.append(" <CertificatePem>\n");
+ buffer.append(" ").append(realm.getCertificatePem()).append("\n");
+ buffer.append(" </CertificatePem>\n");
+ buffer.append(" </Key>\n");
+ buffer.append(" </Keys>\n");
+ }
+ buffer.append(" </IDP>\n");
+ buffer.append(" </SP>\n");
+ buffer.append("</keycloak-saml-adapter>\n");
+ return Response.ok(buffer.toString(), MediaType.TEXT_PLAIN_TYPE).build();
+ }
+
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Keycloak SAML Adapter keycloak-saml.xml";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Keycloak SAML adapter configuration file. Put this in WEB-INF directory if your WAR.";
+ }
+
+ @Override
+ public String getFilename() {
+ return "keycloak-saml.xml";
+ }
+
+ @Override
+ public String getMediaType() {
+ return MediaType.APPLICATION_XML;
+ }
+
+ @Override
+ public boolean isDownloadOnly() {
+ return false;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public ClientInstallationProvider create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "keycloak-saml";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
new file mode 100755
index 0000000..c7bd844
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/ModAuthMellonClientInstallation.java
@@ -0,0 +1,117 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ModAuthMellonClientInstallation implements ClientInstallationProvider {
+ @Override
+ public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+ SamlClient samlClient = new SamlClient(client);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ZipOutputStream zip = new ZipOutputStream(baos);
+ String idpDescriptor = SamlIDPDescriptorClientInstallation.getIDPDescriptorForClient(realm, client, serverBaseUri);
+ String spDescriptor = SamlSPDescriptorClientInstallation.getSPDescriptorForClient(client);
+ String clientDirName = client.getClientId()
+ .replace('/', '_')
+ .replace(' ', '_');
+ try {
+ zip.putNextEntry(new ZipEntry(clientDirName + "/idp-metadata.xml"));
+ zip.write(idpDescriptor.getBytes());
+ zip.closeEntry();
+ zip.putNextEntry(new ZipEntry(clientDirName + "/sp-metadata.xml"));
+ zip.write(spDescriptor.getBytes());
+ zip.closeEntry();
+ if (samlClient.requiresClientSignature()) {
+ if (samlClient.getClientSigningPrivateKey() != null) {
+ zip.putNextEntry(new ZipEntry(clientDirName + "/client-private-key.pem"));
+ zip.write(samlClient.getClientSigningPrivateKey().getBytes());
+ zip.closeEntry();
+ }
+ if (samlClient.getClientSigningCertificate() != null) {
+ zip.putNextEntry(new ZipEntry(clientDirName + "/client-cert.pem"));
+ zip.write(samlClient.getClientSigningCertificate().getBytes());
+ zip.closeEntry();
+ }
+ }
+ zip.close();
+ baos.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+
+ return Response.ok(baos.toByteArray(), getMediaType()).build();
+ }
+
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Mod Auth Mellon files";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "This is a zip file. It contains a SAML SP descriptor, SAML IDP descriptor, private key pem, and certificate pem that you will use to configure mod_auth_mellon for Apache. You'll use these files when crafting the main Apache configuration file. See mod_auth_mellon website for more details.";
+ }
+
+ @Override
+ public String getFilename() {
+ return "keycloak-mod-auth-mellon-sp-config.zip";
+ }
+
+ @Override
+ public String getMediaType() {
+ return "application/zip";
+ }
+
+ @Override
+ public boolean isDownloadOnly() {
+ return true;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public ClientInstallationProvider create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "mod-auth-mellon";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
new file mode 100755
index 0000000..0caee9b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlIDPDescriptorClientInstallation.java
@@ -0,0 +1,120 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.services.resources.RealmsResource;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlIDPDescriptorClientInstallation implements ClientInstallationProvider {
+ public static String getIDPDescriptorForClient(RealmModel realm, ClientModel client, URI serverBaseUri) {
+ SamlClient samlClient = new SamlClient(client);
+ String idpEntityId = RealmsResource.realmBaseUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName()).toString();
+ String idp = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+ "<EntityDescriptor entityID=\"" + idpEntityId + "\"\n" +
+ " xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\"\n" +
+ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
+ " <IDPSSODescriptor WantAuthnRequestsSigned=\"" + Boolean.toString(samlClient.requiresClientSignature()) + "\"\n" +
+ " protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n";
+ if (samlClient.forceNameIDFormat() && samlClient.getNameIDFormat() != null) {
+ idp += " " + samlClient.getNameIDFormat();
+ } else {
+ idp += " <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n" +
+ " <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n" +
+ " <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>\n" +
+ " <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n";
+ }
+ String bindUrl = RealmsResource.protocolUrl(UriBuilder.fromUri(serverBaseUri)).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString();
+ idp += "\n" +
+ " <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
+ " Location=\"" + bindUrl + "\" />\n" +
+ " <SingleLogoutService\n" +
+ " Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n" +
+ " Location=\"" + bindUrl + "\" />\n" +
+ " <KeyDescriptor use=\"signing\">\n" +
+ " <dsig:KeyInfo xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
+ " <dsig:X509Data>\n" +
+ " <dsig:X509Certificate>\n" +
+ " " + realm.getCertificatePem() + "\n" +
+ " </dsig:X509Certificate>\n" +
+ " </dsig:X509Data>\n" +
+ " </dsig:KeyInfo>\n" +
+ " </KeyDescriptor>\n" +
+ " </IDPSSODescriptor>\n" +
+ "</EntityDescriptor>\n";
+ return idp;
+ }
+
+ @Override
+ public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+ String descriptor = getIDPDescriptorForClient(realm, client, serverBaseUri);
+ return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
+ }
+
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "SAML Metadata IDPSSODescriptor";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "SAML Metadata IDSSODescriptor tailored for the client. This is special because not every client may require things like digital signatures";
+ }
+
+ @Override
+ public String getFilename() {
+ return "client-tailored-saml-idp-metadata.xml";
+ }
+
+ public String getMediaType() {
+ return MediaType.APPLICATION_XML;
+ }
+
+ @Override
+ public boolean isDownloadOnly() {
+ return false;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public ClientInstallationProvider create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "saml-idp-descriptor";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
new file mode 100755
index 0000000..0165e30
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -0,0 +1,93 @@
+package org.keycloak.protocol.saml.installation;
+
+import org.keycloak.Config;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.ClientInstallationProvider;
+import org.keycloak.protocol.saml.SamlClient;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.saml.SPMetadataDescriptor;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlSPDescriptorClientInstallation implements ClientInstallationProvider {
+ public static String getSPDescriptorForClient(ClientModel client) {
+ SamlClient samlClient = new SamlClient(client);
+ String assertionUrl = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
+ if (assertionUrl == null) assertionUrl = client.getManagementUrl();
+ String logoutUrl = client.getAttribute(SamlProtocol.SAML_SINGLE_LOGOUT_SERVICE_URL_POST_ATTRIBUTE);
+ if (logoutUrl == null) logoutUrl = client.getManagementUrl();
+ String nameIdFormat = samlClient.getNameIDFormat();
+ if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
+ return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+ }
+
+ @Override
+ public Response generateInstallation(KeycloakSession session, RealmModel realm, ClientModel client, URI serverBaseUri) {
+ String descriptor = getSPDescriptorForClient(client);
+ return Response.ok(descriptor, MediaType.TEXT_PLAIN_TYPE).build();
+ }
+
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "SAML Metadata SPSSODescriptor";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "SAML SP Metadata EntityDescriptor or rather SPSSODescriptor. This is an XML file.";
+ }
+
+ @Override
+ public String getFilename() {
+ return "saml-sp-metadata.xml";
+ }
+
+ public String getMediaType() {
+ return MediaType.APPLICATION_XML;
+ }
+
+ @Override
+ public boolean isDownloadOnly() {
+ return false;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public ClientInstallationProvider create(KeycloakSession session) {
+ return this;
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public String getId() {
+ return "saml-sp-descriptor";
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java b/services/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
new file mode 100755
index 0000000..3afe813
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/JaxrsSAML2BindingBuilder.java
@@ -0,0 +1,76 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.saml.BaseSAML2BindingBuilder;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import javax.ws.rs.core.CacheControl;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.net.URI;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JaxrsSAML2BindingBuilder extends BaseSAML2BindingBuilder<JaxrsSAML2BindingBuilder> {
+ public static class PostBindingBuilder extends BasePostBindingBuilder {
+ public PostBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
+ super(builder, document);
+ }
+ public Response request(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+ return buildResponse(document, actionUrl, true);
+ }
+ public Response response(String actionUrl) throws ConfigurationException, ProcessingException, IOException {
+ return buildResponse(document, actionUrl, false);
+ }
+ protected Response buildResponse(Document responseDoc, String actionUrl, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
+ String str = builder.buildHtmlPostResponse(responseDoc, actionUrl, asRequest);
+
+ return Response.ok(str, MediaType.TEXT_HTML_TYPE)
+ .header("Pragma", "no-cache")
+ .header("Cache-Control", "no-cache, no-store").build();
+ }
+
+
+ }
+
+ public static class RedirectBindingBuilder extends BaseRedirectBindingBuilder {
+ public RedirectBindingBuilder(JaxrsSAML2BindingBuilder builder, Document document) throws ProcessingException {
+ super(builder, document);
+ }
+
+ public Response response(String redirectUri) throws ProcessingException, ConfigurationException, IOException {
+ return response(redirectUri, false);
+ }
+
+ public Response request(String redirect) throws ProcessingException, ConfigurationException, IOException {
+ return response(redirect, true);
+ }
+
+ private Response response(String redirectUri, boolean asRequest) throws ProcessingException, ConfigurationException, IOException {
+ URI uri = generateURI(redirectUri, asRequest);
+ if (logger.isDebugEnabled()) logger.trace("redirect-binding uri: " + uri.toString());
+ CacheControl cacheControl = new CacheControl();
+ cacheControl.setNoCache(true);
+ return Response.status(302).location(uri)
+ .header("Pragma", "no-cache")
+ .header("Cache-Control", "no-cache, no-store").build();
+ }
+
+ }
+
+ public RedirectBindingBuilder redirectBinding(Document document) throws ProcessingException {
+ return new RedirectBindingBuilder(this, document);
+ }
+
+ public PostBindingBuilder postBinding(Document document) throws ProcessingException {
+ return new PostBindingBuilder(this, document);
+ }
+
+
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/AbstractSAMLProtocolMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/AbstractSAMLProtocolMapper.java
new file mode 100755
index 0000000..986a192
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/AbstractSAMLProtocolMapper.java
@@ -0,0 +1,39 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.protocol.saml.SamlProtocol;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class AbstractSAMLProtocolMapper implements ProtocolMapper {
+
+
+ @Override
+ public String getProtocol() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public final ProtocolMapper create(KeycloakSession session) {
+ throw new RuntimeException("UNSUPPORTED METHOD");
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java
new file mode 100755
index 0000000..7de2b29
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/AttributeStatementHelper.java
@@ -0,0 +1,94 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class AttributeStatementHelper {
+ public static final String SAML_ATTRIBUTE_NAME = "attribute.name";
+ public static final String ATTRIBUTE_STATEMENT_CATEGORY = "AttributeStatement Mapper";
+ public static final String FRIENDLY_NAME = "friendly.name";
+ public static final String FRIENDLY_NAME_LABEL = "Friendly Name";
+ public static final String FRIENDLY_NAME_HELP_TEXT = "Standard SAML attribute setting. An optional, more human-readable form of the attribute's name that can be provided if the actual attribute name is cryptic.";
+ public static final String SAML_ATTRIBUTE_NAMEFORMAT = "attribute.nameformat";
+ public static final String BASIC = "Basic";
+ public static final String URI_REFERENCE = "URI Reference";
+ public static final String UNSPECIFIED = "Unspecified";
+
+ public static void addAttribute(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel,
+ String attributeValue) {
+ AttributeType attribute = createAttributeType(mappingModel);
+ attribute.addAttributeValue(attributeValue);
+ attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attribute));
+ }
+
+ public static AttributeType createAttributeType(ProtocolMapperModel mappingModel) {
+ String attributeName = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAME);
+ AttributeType attribute = new AttributeType(attributeName);
+ String attributeType = mappingModel.getConfig().get(SAML_ATTRIBUTE_NAMEFORMAT);
+ String attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_BASIC.get();
+ if ("URI Reference".equals(attributeType)) attributeNameFormat = JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get();
+ else if ("Unspecified".equals(attributeType)) attributeNameFormat = "urn:oasis:names:tc:SAML2.0:attrname-format:unspecified";
+ attribute.setNameFormat(attributeNameFormat);
+ String friendlyName = mappingModel.getConfig().get(FRIENDLY_NAME);
+ if (friendlyName != null && !friendlyName.trim().equals("")) attribute.setFriendlyName(friendlyName);
+ return attribute;
+ }
+
+ public static void setConfigProperties(List<ProviderConfigProperty> configProperties) {
+ ProviderConfigProperty property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.FRIENDLY_NAME);
+ property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
+ property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
+ property.setLabel("SAML Attribute Name");
+ property.setHelpText("SAML Attribute Name");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
+ property.setLabel("SAML Attribute NameFormat");
+ property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified.");
+ List<String> types = new ArrayList(3);
+ types.add(AttributeStatementHelper.BASIC);
+ types.add(AttributeStatementHelper.URI_REFERENCE);
+ types.add(AttributeStatementHelper.UNSPECIFIED);
+ property.setType(ProviderConfigProperty.LIST_TYPE);
+ property.setDefaultValue(types);
+ configProperties.add(property);
+
+ }
+ public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute, String samlAttributeName, String nameFormat, String friendlyName, boolean consentRequired, String consentText, String mapperId) {
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(mapperId);
+ mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ mapper.setConsentRequired(consentRequired);
+ mapper.setConsentText(consentText);
+ Map<String, String> config = new HashMap<String, String>();
+ if (userAttribute != null) config.put(ProtocolMapperUtils.USER_ATTRIBUTE, userAttribute);
+ config.put(SAML_ATTRIBUTE_NAME, samlAttributeName);
+ if (friendlyName != null) {
+ config.put(FRIENDLY_NAME, friendlyName);
+ }
+ if (nameFormat != null) {
+ config.put(SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
+ }
+ mapper.setConfig(config);
+ return mapper;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java
new file mode 100755
index 0000000..e4af4e2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/GroupMembershipMapper.java
@@ -0,0 +1,151 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class GroupMembershipMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+ public static final String PROVIDER_ID = "saml-group-membership-mapper";
+ public static final String SINGLE_GROUP_ATTRIBUTE = "single";
+
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
+ property.setLabel("Group attribute name");
+ property.setDefaultValue("member");
+ property.setHelpText("Name of the SAML attribute you want to put your groups into. i.e. 'member', 'memberOf'.");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.FRIENDLY_NAME);
+ property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
+ property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
+ property.setLabel("SAML Attribute NameFormat");
+ property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified.");
+ List<String> types = new ArrayList(3);
+ types.add(AttributeStatementHelper.BASIC);
+ types.add(AttributeStatementHelper.URI_REFERENCE);
+ types.add(AttributeStatementHelper.UNSPECIFIED);
+ property.setType(ProviderConfigProperty.LIST_TYPE);
+ property.setDefaultValue(types);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(SINGLE_GROUP_ATTRIBUTE);
+ property.setLabel("Single Group Attribute");
+ property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText("If true, all groups will be stored under one attribute with multiple attribute values.");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName("full.path");
+ property.setLabel("Full group path");
+ property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText("Include full path to group i.e. /top/level1/level2, false will just specify the group name");
+ configProperties.add(property);
+
+
+ }
+
+
+ @Override
+ public String getDisplayCategory() {
+ return "Group Mapper";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Group list";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Group names are stored in an attribute value. There is either one attribute with multiple attribute values, or an attribute per group name depending on how you configure it. You can also specify the attribute name i.e. 'member' or 'memberOf' being examples.";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ public static boolean useFullPath(ProtocolMapperModel mappingModel) {
+ return "true".equals(mappingModel.getConfig().get("full.path"));
+ }
+
+
+ @Override
+ public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ String single = mappingModel.getConfig().get(SINGLE_GROUP_ATTRIBUTE);
+ boolean singleAttribute = Boolean.parseBoolean(single);
+
+ boolean fullPath = useFullPath(mappingModel);
+ AttributeType singleAttributeType = null;
+ for (GroupModel group : userSession.getUser().getGroups()) {
+ String groupName;
+ if (fullPath) {
+ groupName = ModelToRepresentation.buildGroupPath(group);
+ } else {
+ groupName = group.getName();
+ }
+ AttributeType attributeType = null;
+ if (singleAttribute) {
+ if (singleAttributeType == null) {
+ singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
+ }
+ attributeType = singleAttributeType;
+ } else {
+ attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ attributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
+ }
+ attributeType.addAttributeValue(groupName);
+ }
+ }
+
+ public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) {
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(PROVIDER_ID);
+ mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ mapper.setConsentRequired(false);
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
+ if (friendlyName != null) {
+ config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
+ }
+ if (nameFormat != null) {
+ config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
+ }
+ config.put(SINGLE_GROUP_ATTRIBUTE, Boolean.toString(singleAttribute));
+ mapper.setConfig(config);
+
+ return mapper;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java
new file mode 100755
index 0000000..086597d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedAttributeMapper.java
@@ -0,0 +1,79 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HardcodedAttributeMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+ public static final String PROVIDER_ID = "saml-hardcode-attribute-mapper";
+ public static final String ATTRIBUTE_VALUE = "attribute.value";
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ AttributeStatementHelper.setConfigProperties(configProperties);
+ property = new ProviderConfigProperty();
+ property.setName(ATTRIBUTE_VALUE);
+ property.setLabel("Attribute value");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ property.setHelpText("Value of the attribute you want to hard code.");
+ configProperties.add(property);
+
+ }
+
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Hardcoded attribute";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Hardcode an attribute into the SAML Assertion.";
+ }
+
+ @Override
+ public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ String attributeValue = mappingModel.getConfig().get(ATTRIBUTE_VALUE);
+ AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
+
+ }
+
+ public static ProtocolMapperModel create(String name,
+ String samlAttributeName, String nameFormat, String friendlyName, String value,
+ boolean consentRequired, String consentText) {
+ String mapperId = PROVIDER_ID;
+ ProtocolMapperModel model = AttributeStatementHelper.createAttributeMapper(name, null, samlAttributeName, nameFormat, friendlyName,
+ consentRequired, consentText, mapperId);
+ model.getConfig().put(ATTRIBUTE_VALUE, value);
+ return model;
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
new file mode 100755
index 0000000..e312281
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/HardcodedRole.java
@@ -0,0 +1,72 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.protocol.saml.SamlProtocol;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class HardcodedRole extends AbstractSAMLProtocolMapper {
+ public static final String PROVIDER_ID = "saml-hardcode-role-mapper";
+ public static final String ATTRIBUTE_VALUE = "attribute.value";
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName("role");
+ property.setLabel("Role");
+ property.setHelpText("Arbitrary role name you want to hardcode. This role does not have to exist in current realm and can be just any string you need");
+ property.setType(ProviderConfigProperty.ROLE_TYPE);
+ configProperties.add(property);
+ }
+
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Hardcoded role";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Hardcode role into SAML Assertion.";
+ }
+
+ public static ProtocolMapperModel create(String name,
+ String role) {
+ String mapperId = PROVIDER_ID;
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(mapperId);
+ mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ Map<String, String> config = new HashMap<String, String>();
+ config.put("role", role);
+ mapper.setConfig(config);
+ return mapper;
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
new file mode 100755
index 0000000..d59fc48
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleListMapper.java
@@ -0,0 +1,172 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.dom.saml.v2.assertion.AttributeType;
+import org.keycloak.services.managers.ClientSessionCode;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleListMapper extends AbstractSAMLProtocolMapper implements SAMLRoleListMapper {
+ public static final String PROVIDER_ID = "saml-role-list-mapper";
+ public static final String SINGLE_ROLE_ATTRIBUTE = "single";
+
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAME);
+ property.setLabel("Role attribute name");
+ property.setDefaultValue("Role");
+ property.setHelpText("Name of the SAML attribute you want to put your roles into. i.e. 'Role', 'memberOf'.");
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.FRIENDLY_NAME);
+ property.setLabel(AttributeStatementHelper.FRIENDLY_NAME_LABEL);
+ property.setHelpText(AttributeStatementHelper.FRIENDLY_NAME_HELP_TEXT);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT);
+ property.setLabel("SAML Attribute NameFormat");
+ property.setHelpText("SAML Attribute NameFormat. Can be basic, URI reference, or unspecified.");
+ List<String> types = new ArrayList(3);
+ types.add(AttributeStatementHelper.BASIC);
+ types.add(AttributeStatementHelper.URI_REFERENCE);
+ types.add(AttributeStatementHelper.UNSPECIFIED);
+ property.setType(ProviderConfigProperty.LIST_TYPE);
+ property.setDefaultValue(types);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(SINGLE_ROLE_ATTRIBUTE);
+ property.setLabel("Single Role Attribute");
+ property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
+ property.setDefaultValue("true");
+ property.setHelpText("If true, all roles will be stored under one attribute with multiple attribute values.");
+ configProperties.add(property);
+
+ }
+
+
+ @Override
+ public String getDisplayCategory() {
+ return "Role Mapper";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Role list";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Role names are stored in an attribute value. There is either one attribute with multiple attribute values, or an attribute per role name depending on how you configure it. You can also specify the attribute name i.e. 'Role' or 'memberOf' being examples.";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public void mapRoles(AttributeStatementType roleAttributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ String single = mappingModel.getConfig().get(SINGLE_ROLE_ATTRIBUTE);
+ boolean singleAttribute = Boolean.parseBoolean(single);
+
+ List<SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper>> roleNameMappers = new LinkedList<>();
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ AttributeType singleAttributeType = null;
+ Set<ProtocolMapperModel> requestedProtocolMappers = new ClientSessionCode(clientSession.getRealm(), clientSession).getRequestedProtocolMappers();
+ for (ProtocolMapperModel mapping : requestedProtocolMappers) {
+
+ ProtocolMapper mapper = (ProtocolMapper)sessionFactory.getProviderFactory(ProtocolMapper.class, mapping.getProtocolMapper());
+ if (mapper == null) continue;
+ if (mapper instanceof SAMLRoleNameMapper) {
+ roleNameMappers.add(new SamlProtocol.ProtocolMapperProcessor<>((SAMLRoleNameMapper) mapper,mapping));
+ }
+ if (mapper instanceof HardcodedRole) {
+ AttributeType attributeType = null;
+ if (singleAttribute) {
+ if (singleAttributeType == null) {
+ singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
+ }
+ attributeType = singleAttributeType;
+ } else {
+ attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
+ }
+ attributeType.addAttributeValue(mapping.getConfig().get("role"));
+ }
+ }
+
+ for (String roleId : clientSession.getRoles()) {
+ // todo need a role mapping
+ RoleModel roleModel = clientSession.getRealm().getRoleById(roleId);
+ AttributeType attributeType = null;
+ if (singleAttribute) {
+ if (singleAttributeType == null) {
+ singleAttributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(singleAttributeType));
+ }
+ attributeType = singleAttributeType;
+ } else {
+ attributeType = AttributeStatementHelper.createAttributeType(mappingModel);
+ roleAttributeStatement.addAttribute(new AttributeStatementType.ASTChoiceType(attributeType));
+ }
+ String roleName = roleModel.getName();
+ for (SamlProtocol.ProtocolMapperProcessor<SAMLRoleNameMapper> entry : roleNameMappers) {
+ String newName = entry.mapper.mapName(entry.model, roleModel);
+ if (newName != null) {
+ roleName = newName;
+ break;
+ }
+ }
+ attributeType.addAttributeValue(roleName);
+ }
+
+ }
+
+ public static ProtocolMapperModel create(String name, String samlAttributeName, String nameFormat, String friendlyName, boolean singleAttribute) {
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(PROVIDER_ID);
+ mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ mapper.setConsentRequired(false);
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, samlAttributeName);
+ if (friendlyName != null) {
+ config.put(AttributeStatementHelper.FRIENDLY_NAME, friendlyName);
+ }
+ if (nameFormat != null) {
+ config.put(AttributeStatementHelper.SAML_ATTRIBUTE_NAMEFORMAT, nameFormat);
+ }
+ config.put(SINGLE_ROLE_ATTRIBUTE, Boolean.toString(singleAttribute));
+ mapper.setConfig(config);
+
+ return mapper;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java
new file mode 100755
index 0000000..ea91530
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/RoleNameMapper.java
@@ -0,0 +1,128 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.Config;
+import org.keycloak.models.*;
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.protocol.saml.SamlProtocol;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Map an assigned role to a different position and name in the token
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleNameMapper implements SAMLRoleNameMapper, ProtocolMapper {
+
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ public static final String ROLE_CONFIG = "role";
+ public static String NEW_ROLE_NAME = "new.role.name";
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(ROLE_CONFIG);
+ property.setLabel("Role");
+ property.setHelpText("Role name you want changed. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
+ property.setType(ProviderConfigProperty.ROLE_TYPE);
+ configProperties.add(property);
+ property = new ProviderConfigProperty();
+ property.setName(NEW_ROLE_NAME);
+ property.setLabel("New Role Name");
+ property.setHelpText("The new role name.");
+ property.setType(ProviderConfigProperty.STRING_TYPE);
+ configProperties.add(property);
+ }
+
+ public static final String PROVIDER_ID = "saml-role-name-mapper";
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ public String getDisplayType() {
+ return "Role Name Mapper";
+ }
+
+ public String getDisplayCategory() {
+ return "Role Mapper";
+
+ }
+
+ public String getHelpText() {
+ return "Map an assigned role to a new name";
+ }
+
+ @Override
+ public String mapName(ProtocolMapperModel model, RoleModel roleModel) {
+ RoleContainerModel container = roleModel.getContainer();
+ ClientModel app = null;
+ if (container instanceof ClientModel) {
+ app = ((ClientModel) container);
+ }
+ String role = model.getConfig().get(ROLE_CONFIG);
+ String newName = model.getConfig().get(NEW_ROLE_NAME);
+ String appName = null;
+ int scopeIndex = role.indexOf('.');
+ if (scopeIndex > -1) {
+ if (app == null) return null;
+ appName = role.substring(0, scopeIndex);
+ if (!app.getClientId().equals(appName)) return null;
+ role = role.substring(scopeIndex + 1);
+ } else {
+ if (app != null) return null;
+ }
+ if (roleModel.getName().equals(role)) return newName;
+ return null;
+ }
+
+ public static ProtocolMapperModel create(String name,
+ String role,
+ String newName) {
+ String mapperId = PROVIDER_ID;
+ ProtocolMapperModel mapper = new ProtocolMapperModel();
+ mapper.setName(name);
+ mapper.setProtocolMapper(mapperId);
+ mapper.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
+ Map<String, String> config = new HashMap<String, String>();
+ config.put(ROLE_CONFIG, role);
+ config.put(NEW_ROLE_NAME, newName);
+ mapper.setConfig(config);
+ return mapper;
+
+ }
+
+ @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/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java
new file mode 100755
index 0000000..09b9b10
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLAttributeStatementMapper.java
@@ -0,0 +1,17 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLAttributeStatementMapper {
+
+ void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionModel clientSession);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java
new file mode 100755
index 0000000..87a48c9
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLGroupNameMapper.java
@@ -0,0 +1,12 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.ProtocolMapperModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLGroupNameMapper {
+ public String mapName(ProtocolMapperModel model, GroupModel group);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLLoginResponseMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLLoginResponseMapper.java
new file mode 100755
index 0000000..1fcb5df
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLLoginResponseMapper.java
@@ -0,0 +1,17 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.dom.saml.v2.protocol.ResponseType;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLLoginResponseMapper {
+
+ ResponseType transformLoginResponse(ResponseType response, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionModel clientSession);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java
new file mode 100755
index 0000000..16347c6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleListMapper.java
@@ -0,0 +1,17 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLRoleListMapper {
+
+ void mapRoles(AttributeStatementType roleAttributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session,
+ UserSessionModel userSession, ClientSessionModel clientSession);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleNameMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleNameMapper.java
new file mode 100755
index 0000000..f3ecbd4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/SAMLRoleNameMapper.java
@@ -0,0 +1,12 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RoleModel;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SAMLRoleNameMapper {
+ public String mapName(ProtocolMapperModel model, RoleModel role);
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java
new file mode 100755
index 0000000..8f5e3b8
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserAttributeStatementMapper.java
@@ -0,0 +1,81 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mappings UserModel attribute (not property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
+ property.setLabel(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_LABEL);
+ property.setHelpText(ProtocolMapperUtils.USER_MODEL_ATTRIBUTE_HELP_TEXT);
+ configProperties.add(property);
+ AttributeStatementHelper.setConfigProperties(configProperties);
+
+ }
+
+ public static final String PROVIDER_ID = "saml-user-attribute-mapper";
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "User Attribute";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Map a custom user attribute to a to a SAML attribute.";
+ }
+
+ @Override
+ public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ UserModel user = userSession.getUser();
+ String attributeName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
+ String attributeValue = KeycloakModelUtils.resolveFirstAttribute(user, attributeName);
+ if (attributeValue == null) return;
+ AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, attributeValue);
+
+ }
+
+ public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute,
+ String samlAttributeName, String nameFormat, String friendlyName,
+ boolean consentRequired, String consentText) {
+ String mapperId = PROVIDER_ID;
+ return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, nameFormat, friendlyName,
+ consentRequired, consentText, mapperId);
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java
new file mode 100755
index 0000000..e0518bc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserPropertyAttributeStatementMapper.java
@@ -0,0 +1,79 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.ProtocolMapperUtils;
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mappings UserModel property (the property name of a getter method) to an AttributeStatement.
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserPropertyAttributeStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName(ProtocolMapperUtils.USER_ATTRIBUTE);
+ property.setLabel(ProtocolMapperUtils.USER_MODEL_PROPERTY_LABEL);
+ property.setHelpText(ProtocolMapperUtils.USER_MODEL_PROPERTY_HELP_TEXT);
+ configProperties.add(property);
+ AttributeStatementHelper.setConfigProperties(configProperties);
+
+ }
+
+ public static final String PROVIDER_ID = "saml-user-property-mapper";
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "User Property";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Map a built in user property to a SAML attribute type.";
+ }
+
+ @Override
+ public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ UserModel user = userSession.getUser();
+ String propertyName = mappingModel.getConfig().get(ProtocolMapperUtils.USER_ATTRIBUTE);
+ String propertyValue = ProtocolMapperUtils.getUserModelValue(user, propertyName);
+ if (propertyValue == null) return;
+ AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, propertyValue);
+
+ }
+
+ public static ProtocolMapperModel createAttributeMapper(String name, String userAttribute,
+ String samlAttributeName, String nameFormat, String friendlyName,
+ boolean consentRequired, String consentText) {
+ String mapperId = PROVIDER_ID;
+ return AttributeStatementHelper.createAttributeMapper(name, userAttribute, samlAttributeName, nameFormat, friendlyName,
+ consentRequired, consentText, mapperId);
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/mappers/UserSessionNoteStatementMapper.java b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserSessionNoteStatementMapper.java
new file mode 100755
index 0000000..445555b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/mappers/UserSessionNoteStatementMapper.java
@@ -0,0 +1,67 @@
+package org.keycloak.protocol.saml.mappers;
+
+import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Maps a user session note to a SAML attribute
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserSessionNoteStatementMapper extends AbstractSAMLProtocolMapper implements SAMLAttributeStatementMapper {
+ private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty property;
+ property = new ProviderConfigProperty();
+ property.setName("note");
+ property.setLabel("User Session Note Attribute");
+ property.setHelpText("The user session note you want to grab the value from.");
+ configProperties.add(property);
+ AttributeStatementHelper.setConfigProperties(configProperties);
+
+ }
+
+ public static final String PROVIDER_ID = "saml-user-session-note-mapper";
+
+
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return configProperties;
+ }
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "User Session Note";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return AttributeStatementHelper.ATTRIBUTE_STATEMENT_CATEGORY;
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Map a user session note to a SAML attribute.";
+ }
+
+ @Override
+ public void transformAttributeStatement(AttributeStatementType attributeStatement, ProtocolMapperModel mappingModel, KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession) {
+ String note = mappingModel.getConfig().get("note");
+ String value = userSession.getNote(note);
+ if (value == null) return;
+ AttributeStatementHelper.addAttribute(attributeStatement, mappingModel, value);
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java
new file mode 100755
index 0000000..41ac46c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/authenticator/HttpBasicAuthenticator.java
@@ -0,0 +1,171 @@
+package org.keycloak.protocol.saml.profile.ecp.authenticator;
+
+import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticationFlowContext;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.Authenticator;
+import org.keycloak.authentication.AuthenticatorFactory;
+import org.keycloak.common.util.Base64;
+import org.keycloak.events.Errors;
+import org.keycloak.models.*;
+import org.keycloak.models.AuthenticationExecutionModel.Requirement;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class HttpBasicAuthenticator implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "http-basic-authenticator";
+
+ @Override
+ public String getDisplayType() {
+ return null;
+ }
+
+ @Override
+ public String getReferenceCategory() {
+ return null;
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return false;
+ }
+
+ @Override
+ public Requirement[] getRequirementChoices() {
+ return new Requirement[0];
+ }
+
+ @Override
+ public boolean isUserSetupAllowed() {
+ return false;
+ }
+
+ @Override
+ public String getHelpText() {
+ return null;
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return null;
+ }
+
+ @Override
+ public Authenticator create(KeycloakSession session) {
+ return new Authenticator() {
+
+ private static final String BASIC = "Basic";
+ private static final String BASIC_PREFIX = BASIC + " ";
+
+ @Override
+ public void authenticate(AuthenticationFlowContext context) {
+ HttpRequest httpRequest = context.getHttpRequest();
+ HttpHeaders httpHeaders = httpRequest.getHttpHeaders();
+ String[] usernameAndPassword = getUsernameAndPassword(httpHeaders);
+
+ context.attempted();
+
+ if (usernameAndPassword != null) {
+ RealmModel realm = context.getRealm();
+ UserModel user = context.getSession().users().getUserByUsername(usernameAndPassword[0], realm);
+
+ if (user != null) {
+ String password = usernameAndPassword[1];
+ boolean valid = context.getSession().users().validCredentials(context.getSession(), realm, user, UserCredentialModel.password(password));
+
+ if (valid) {
+ context.getClientSession().setAuthenticatedUser(user);
+ context.success();
+ } else {
+ context.getEvent().user(user);
+ context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
+ context.failure(AuthenticationFlowError.INVALID_USER, Response.status(Response.Status.UNAUTHORIZED)
+ .header(HttpHeaders.WWW_AUTHENTICATE, BASIC_PREFIX + "realm=\"" + realm.getName() + "\"")
+ .build());
+ }
+ }
+ }
+ }
+
+ private String[] getUsernameAndPassword(HttpHeaders httpHeaders) {
+ List<String> authHeaders = httpHeaders.getRequestHeader(HttpHeaders.AUTHORIZATION);
+
+ if (authHeaders == null || authHeaders.size() == 0) {
+ return null;
+ }
+
+ String credentials = null;
+
+ for (String authHeader : authHeaders) {
+ if (authHeader.startsWith(BASIC_PREFIX)) {
+ String[] split = authHeader.trim().split("\\s+");
+
+ if (split == null || split.length != 2) return null;
+
+ credentials = split[1];
+ }
+ }
+
+ try {
+ return new String(Base64.decode(credentials)).split(":");
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to parse credentials.", e);
+ }
+ }
+
+ @Override
+ public void action(AuthenticationFlowContext context) {
+
+ }
+
+ @Override
+ public boolean requiresUser() {
+ return false;
+ }
+
+ @Override
+ public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
+ return false;
+ }
+
+ @Override
+ public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ };
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
new file mode 100755
index 0000000..2885f38
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/SamlEcpProfileService.java
@@ -0,0 +1,140 @@
+package org.keycloak.protocol.saml.profile.ecp;
+
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.DefaultAuthenticationFlows;
+import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
+import org.keycloak.protocol.saml.SamlConfigAttributes;
+import org.keycloak.protocol.saml.SamlProtocol;
+import org.keycloak.protocol.saml.SamlService;
+import org.keycloak.protocol.saml.profile.ecp.util.Soap;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.common.exceptions.ConfigurationException;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.w3c.dom.Document;
+
+import javax.ws.rs.core.Response;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPHeaderElement;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public class SamlEcpProfileService extends SamlService {
+
+ private static final String NS_PREFIX_PROFILE_ECP = "ecp";
+ private static final String NS_PREFIX_SAML_PROTOCOL = "samlp";
+ private static final String NS_PREFIX_SAML_ASSERTION = "saml";
+
+ public SamlEcpProfileService(RealmModel realm, EventBuilder event) {
+ super(realm, event);
+ }
+
+ public Response authenticate(InputStream inputStream) {
+ try {
+ return new PostBindingProtocol() {
+ @Override
+ protected String getBindingType(AuthnRequestType requestAbstractType) {
+ return SamlProtocol.SAML_SOAP_BINDING;
+ }
+
+ @Override
+ protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
+ // force passive authentication when executing this profile
+ requestAbstractType.setIsPassive(true);
+ requestAbstractType.setDestination(uriInfo.getAbsolutePath());
+ return super.loginRequest(relayState, requestAbstractType, client);
+ }
+ }.execute(Soap.toSamlHttpPostMessage(inputStream), null, null);
+ } catch (Exception e) {
+ String reason = "Some error occurred while processing the AuthnRequest.";
+ String detail = e.getMessage();
+
+ if (detail == null) {
+ detail = reason;
+ }
+
+ return Soap.createFault().reason(reason).detail(detail).build();
+ }
+ }
+
+ @Override
+ protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) {
+ return super.newBrowserAuthentication(clientSession, isPassive, createEcpSamlProtocol());
+ }
+
+ private SamlProtocol createEcpSamlProtocol() {
+ return new SamlProtocol() {
+ // method created to send a SOAP Binding response instead of a HTTP POST response
+ @Override
+ protected Response buildAuthenticatedResponse(ClientSessionModel clientSession, String redirectUri, Document samlDocument, JaxrsSAML2BindingBuilder bindingBuilder) throws ConfigurationException, ProcessingException, IOException {
+ Document document = bindingBuilder.postBinding(samlDocument).getDocument();
+
+ try {
+ Soap.SoapMessageBuilder messageBuilder = Soap.createMessage()
+ .addNamespace(NS_PREFIX_SAML_ASSERTION, JBossSAMLURIConstants.ASSERTION_NSURI.get())
+ .addNamespace(NS_PREFIX_SAML_PROTOCOL, JBossSAMLURIConstants.PROTOCOL_NSURI.get())
+ .addNamespace(NS_PREFIX_PROFILE_ECP, JBossSAMLURIConstants.ECP_PROFILE.get());
+
+ createEcpResponseHeader(redirectUri, messageBuilder);
+ createRequestAuthenticatedHeader(clientSession, messageBuilder);
+
+ messageBuilder.addToBody(document);
+
+ return messageBuilder.build();
+ } catch (Exception e) {
+ throw new RuntimeException("Error while creating SAML response.", e);
+ }
+ }
+
+ private void createRequestAuthenticatedHeader(ClientSessionModel clientSession, Soap.SoapMessageBuilder messageBuilder) {
+ ClientModel client = clientSession.getClient();
+
+ if ("true".equals(client.getAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE))) {
+ SOAPHeaderElement ecpRequestAuthenticated = messageBuilder.addHeader(JBossSAMLConstants.REQUEST_AUTHENTICATED.get(), NS_PREFIX_PROFILE_ECP);
+
+ ecpRequestAuthenticated.setMustUnderstand(true);
+ ecpRequestAuthenticated.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+ }
+ }
+
+ private void createEcpResponseHeader(String redirectUri, Soap.SoapMessageBuilder messageBuilder) throws SOAPException {
+ SOAPHeaderElement ecpResponseHeader = messageBuilder.addHeader(JBossSAMLConstants.RESPONSE.get(), NS_PREFIX_PROFILE_ECP);
+
+ ecpResponseHeader.setMustUnderstand(true);
+ ecpResponseHeader.setActor("http://schemas.xmlsoap.org/soap/actor/next");
+ ecpResponseHeader.addAttribute(messageBuilder.createName(JBossSAMLConstants.ASSERTION_CONSUMER_SERVICE_URL.get()), redirectUri);
+ }
+
+ @Override
+ protected Response buildErrorResponse(ClientSessionModel clientSession, JaxrsSAML2BindingBuilder binding, Document document) throws ConfigurationException, ProcessingException, IOException {
+ return Soap.createMessage().addToBody(document).build();
+ }
+
+ @Override
+ protected Response buildLogoutResponse(UserSessionModel userSession, String logoutBindingUri, SAML2LogoutResponseBuilder builder, JaxrsSAML2BindingBuilder binding) throws ConfigurationException, ProcessingException, IOException {
+ return Soap.createFault().reason("Logout not supported.").build();
+ }
+ }.setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo);
+ }
+
+ @Override
+ protected AuthenticationFlowModel getAuthenticationFlow() {
+ for (AuthenticationFlowModel flowModel : realm.getAuthenticationFlows()) {
+ if (flowModel.getAlias().equals(DefaultAuthenticationFlows.SAML_ECP_FLOW)) {
+ return flowModel;
+ }
+ }
+
+ throw new RuntimeException("Could not resolve authentication flow for SAML ECP Profile.");
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java
new file mode 100644
index 0000000..4bdf76a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/profile/ecp/util/Soap.java
@@ -0,0 +1,177 @@
+package org.keycloak.protocol.saml.profile.ecp.util;
+
+import org.keycloak.saml.common.constants.JBossSAMLConstants;
+import org.keycloak.saml.processing.core.saml.v2.util.DocumentUtil;
+import org.keycloak.saml.processing.web.util.PostBindingUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.Name;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPFault;
+import javax.xml.soap.SOAPHeaderElement;
+import javax.xml.soap.SOAPMessage;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Locale;
+
+/**
+ * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
+ */
+public final class Soap {
+
+ public static SoapFaultBuilder createFault() {
+ return new SoapFaultBuilder();
+ }
+
+ public static SoapMessageBuilder createMessage() {
+ return new SoapMessageBuilder();
+ }
+
+ /**
+ * <p>Returns a string encoded accordingly with the SAML HTTP POST Binding specification based on the
+ * given <code>inputStream</code> which must contain a valid SOAP message.
+ *
+ * <p>The resulting string is based on the Body of the SOAP message, which should map to a valid SAML message.
+ *
+ * @param inputStream the input stream containing a valid SOAP message with a Body that contains a SAML message
+ *
+ * @return a string encoded accordingly with the SAML HTTP POST Binding specification
+ */
+ public static String toSamlHttpPostMessage(InputStream inputStream) {
+ try {
+ MessageFactory messageFactory = MessageFactory.newInstance();
+ SOAPMessage soapMessage = messageFactory.createMessage(null, inputStream);
+ SOAPBody soapBody = soapMessage.getSOAPBody();
+ Node authnRequestNode = soapBody.getFirstChild();
+ Document document = DocumentUtil.createDocument();
+
+ document.appendChild(document.importNode(authnRequestNode, true));
+
+ return PostBindingUtil.base64Encode(DocumentUtil.asString(document));
+ } catch (Exception e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ }
+
+ public static class SoapMessageBuilder {
+ private final SOAPMessage message;
+ private final SOAPBody body;
+ private final SOAPEnvelope envelope;
+
+ private SoapMessageBuilder() {
+ try {
+ this.message = MessageFactory.newInstance().createMessage();
+ this.envelope = message.getSOAPPart().getEnvelope();
+ this.body = message.getSOAPBody();
+ } catch (Exception e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ }
+
+ public SoapMessageBuilder addToBody(Document document) {
+ try {
+ this.body.addDocument(document);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Could not add document to SOAP body.", e);
+ }
+ return this;
+ }
+
+ public SoapMessageBuilder addNamespace(String prefix, String ns) {
+ try {
+ envelope.addNamespaceDeclaration(prefix, ns);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Could not add namespace to SOAP Envelope.", e);
+ }
+ return this;
+ }
+
+ public SOAPHeaderElement addHeader(String name, String prefix) {
+ try {
+ return this.envelope.getHeader().addHeaderElement(envelope.createQName(name, prefix));
+ } catch (SOAPException e) {
+ throw new RuntimeException("Could not add SOAP Header.", e);
+ }
+ }
+
+ public Name createName(String name) {
+ try {
+ return this.envelope.createName(name);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Could not create Name.", e);
+ }
+ }
+
+ public Response build() {
+ return build(Status.OK);
+ }
+
+ Response build(Status status) {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ try {
+ this.message.writeTo(outputStream);
+ } catch (Exception e) {
+ throw new RuntimeException("Error while building SOAP Fault.", e);
+ }
+
+ return Response.status(status).entity(outputStream.toByteArray()).build();
+ }
+
+ SOAPMessage getMessage() {
+ return this.message;
+ }
+ }
+
+ public static class SoapFaultBuilder {
+
+ private final SOAPFault fault;
+ private final SoapMessageBuilder messageBuilder;
+
+ private SoapFaultBuilder() {
+ this.messageBuilder = createMessage();
+ try {
+ this.fault = messageBuilder.getMessage().getSOAPBody().addFault();
+ } catch (SOAPException e) {
+ throw new RuntimeException("Could not create SOAP Fault.", e);
+ }
+ }
+
+ public SoapFaultBuilder detail(String detail) {
+ try {
+ this.fault.addDetail().setValue(detail);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ return this;
+ }
+
+ public SoapFaultBuilder reason(String reason) {
+ try {
+ this.fault.setFaultString(reason);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ return this;
+ }
+
+ public SoapFaultBuilder code(String code) {
+ try {
+ this.fault.setFaultCode(code);
+ } catch (SOAPException e) {
+ throw new RuntimeException("Error creating fault message.", e);
+ }
+ return this;
+ }
+
+ public Response build() {
+ return this.messageBuilder.build(Status.INTERNAL_SERVER_ERROR);
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
new file mode 100755
index 0000000..b6df68f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClient.java
@@ -0,0 +1,159 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.models.ClientConfigResolver;
+import org.keycloak.models.ClientModel;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlClient extends ClientConfigResolver {
+
+ public SamlClient(ClientModel client) {
+ super(client);
+ }
+
+ public String getCanonicalizationMethod() {
+ return resolveAttribute(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
+ }
+
+ public void setCanonicalizationMethod(String value) {
+ client.setAttribute(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE, value);
+ }
+
+ public SignatureAlgorithm getSignatureAlgorithm() {
+ String alg = resolveAttribute(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM);
+ if (alg != null) {
+ SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
+ if (algorithm != null)
+ return algorithm;
+ }
+ return SignatureAlgorithm.RSA_SHA256;
+ }
+
+ public void setSignatureAlgorithm(SignatureAlgorithm algorithm) {
+ client.setAttribute(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, algorithm.name());
+ }
+
+ public String getNameIDFormat() {
+ String nameIdFormat = null;
+
+ String configuredNameIdFormat = resolveAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE);
+ if (configuredNameIdFormat != null) {
+ if (configuredNameIdFormat.equals("email")) {
+ nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get();
+ } else if (configuredNameIdFormat.equals("persistent")) {
+ nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get();
+ } else if (configuredNameIdFormat.equals("transient")) {
+ nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get();
+ } else if (configuredNameIdFormat.equals("username")) {
+ nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
+ } else {
+ nameIdFormat = JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get();
+ }
+ }
+ return nameIdFormat;
+
+ }
+ public void setNameIDFormat(String format) {
+ client.setAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE, format);
+ }
+
+ public boolean includeAuthnStatement() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT));
+ }
+
+ public void setIncludeAuthnStatement(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT, Boolean.toString(val));
+ }
+
+ public boolean forceNameIDFormat() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE));
+
+ }
+ public void setForceNameIDFormat(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE, Boolean.toString(val));
+ }
+
+ public boolean requiresRealmSignature() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE));
+ }
+
+ public void setRequiresRealmSignature(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+
+ }
+
+ public boolean forcePostBinding() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_FORCE_POST_BINDING));
+ }
+
+ public void setForcePostBinding(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_FORCE_POST_BINDING, Boolean.toString(val));
+
+ }
+ public boolean requiresAssertionSignature() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE));
+ }
+
+ public void setRequiresAssertionSignature(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE , Boolean.toString(val));
+
+ }
+ public boolean requiresEncryption() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_ENCRYPT));
+ }
+
+
+ public void setRequiresEncryption(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_ENCRYPT, Boolean.toString(val));
+
+ }
+
+ public boolean requiresClientSignature() {
+ return "true".equals(resolveAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE));
+ }
+
+ public void setRequiresClientSignature(boolean val) {
+ client.setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE , Boolean.toString(val));
+
+ }
+
+ public String getClientSigningCertificate() {
+ return client.getAttribute(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE);
+ }
+
+ public void setClientSigningCertificate(String val) {
+ client.setAttribute(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, val);
+
+ }
+
+ public String getClientSigningPrivateKey() {
+ return client.getAttribute(SamlConfigAttributes.SAML_SIGNING_PRIVATE_KEY);
+ }
+
+ public void setClientSigningPrivateKey(String val) {
+ client.setAttribute(SamlConfigAttributes.SAML_SIGNING_PRIVATE_KEY, val);
+
+ }
+
+ public String getClientEncryptingCertificate() {
+ return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
+ }
+
+ public void setClientEncryptingCertificate(String val) {
+ client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, val);
+
+ }
+ public String getClientEncryptingPrivateKey() {
+ return client.getAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE);
+ }
+
+ public void setClientEncryptingPrivateKey(String val) {
+ client.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE, val);
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
new file mode 100755
index 0000000..3667fda
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlClientTemplate.java
@@ -0,0 +1,130 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.saml.SignatureAlgorithm;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlClientTemplate {
+ protected ClientTemplateModel clientTemplate;
+
+ public SamlClientTemplate(ClientTemplateModel template) {
+ this.clientTemplate = template;
+ }
+
+ public String getId() {
+ return clientTemplate.getId();
+ }
+
+//
+
+ public String getCanonicalizationMethod() {
+ return clientTemplate.getAttribute(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
+ }
+
+ public void setCanonicalizationMethod(String value) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE, value);
+ }
+
+ public SignatureAlgorithm getSignatureAlgorithm() {
+ String alg = null;
+ alg = clientTemplate.getAttribute(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
+ if (alg != null) {
+ SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
+ if (algorithm != null)
+ return algorithm;
+ }
+ return SignatureAlgorithm.RSA_SHA256;
+ }
+
+ public void setSignatureAlgorithm(SignatureAlgorithm algorithm) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM, algorithm.name());
+ }
+
+ public String getNameIDFormat() {
+ return clientTemplate.getAttributes().get(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE);
+ }
+ public void setNameIDFormat(String format) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE, format);
+ }
+
+ public boolean includeAuthnStatement() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT));
+ }
+
+ public void setIncludeAuthnStatement(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_AUTHNSTATEMENT, Boolean.toString(val));
+ }
+
+ public boolean forceNameIDFormat() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE));
+
+ }
+ public void setForceNameIDFormat(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE, Boolean.toString(val));
+ }
+
+ public boolean requiresRealmSignature() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE));
+ }
+
+ public void setRequiresRealmSignature(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(val));
+
+ }
+
+ public boolean forcePostBinding() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_FORCE_POST_BINDING));
+ }
+
+ public void setForcePostBinding(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_FORCE_POST_BINDING, Boolean.toString(val));
+
+ }
+ public boolean requiresAssertionSignature() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE));
+ }
+
+ public void setRequiresAssertionSignature(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE , Boolean.toString(val));
+
+ }
+ public boolean requiresEncryption() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_ENCRYPT));
+ }
+
+
+ public void setRequiresEncryption(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_ENCRYPT, Boolean.toString(val));
+
+ }
+
+ public boolean requiresClientSignature() {
+ return "true".equals(clientTemplate.getAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE));
+ }
+
+ public void setRequiresClientSignature(boolean val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE , Boolean.toString(val));
+
+ }
+
+ public String getClientSigningCertificate() {
+ return clientTemplate.getAttribute(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE);
+ }
+
+ public void setClientSigningCertificate(String val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, val);
+
+ }
+
+ public String getClientSigningPrivateKey() {
+ return clientTemplate.getAttribute(SamlConfigAttributes.SAML_SIGNING_PRIVATE_KEY);
+ }
+
+ public void setClientSigningPrivateKey(String val) {
+ clientTemplate.setAttribute(SamlConfigAttributes.SAML_SIGNING_PRIVATE_KEY, val);
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
new file mode 100755
index 0000000..c6bc60a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlConfigAttributes.java
@@ -0,0 +1,24 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface SamlConfigAttributes {
+ String SAML_SIGNING_PRIVATE_KEY = "saml.signing.private.key";
+ String SAML_CANONICALIZATION_METHOD_ATTRIBUTE = "saml_signature_canonicalization_method";
+ String SAML_SIGNATURE_ALGORITHM = "saml.signature.algorithm";
+ String SAML_NAME_ID_FORMAT_ATTRIBUTE = "saml_name_id_format";
+ String SAML_AUTHNSTATEMENT = "saml.authnstatement";
+ String SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE = "saml_force_name_id_format";
+ String SAML_SERVER_SIGNATURE = "saml.server.signature";
+ String SAML_FORCE_POST_BINDING = "saml.force.post.binding";
+ String SAML_ASSERTION_SIGNATURE = "saml.assertion.signature";
+ String SAML_ENCRYPT = "saml.encrypt";
+ String SAML_CLIENT_SIGNATURE_ATTRIBUTE = "saml.client.signature";
+ String SAML_SIGNING_CERTIFICATE_ATTRIBUTE = "saml.signing." + ClientAttributeCertificateResource.X509CERTIFICATE;
+ String SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE = "saml.encryption." + ClientAttributeCertificateResource.X509CERTIFICATE;
+ String SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE = "saml.encryption." + ClientAttributeCertificateResource.PRIVATE_KEY;
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
new file mode 100755
index 0000000..3223d80
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolFactory.java
@@ -0,0 +1,191 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.Config;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ProtocolMapperModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.AbstractLoginProtocolFactory;
+import org.keycloak.protocol.LoginProtocol;
+import org.keycloak.protocol.saml.mappers.AttributeStatementHelper;
+import org.keycloak.protocol.saml.mappers.RoleListMapper;
+import org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper;
+import org.keycloak.representations.idm.CertificateRepresentation;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ClientTemplateRepresentation;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
+
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlProtocolFactory extends AbstractLoginProtocolFactory {
+
+ @Override
+ public Object createProtocolEndpoint(RealmModel realm, EventBuilder event) {
+ return new SamlService(realm, event);
+ }
+
+ @Override
+ public LoginProtocol create(KeycloakSession session) {
+ return new SamlProtocol().setSession(session);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ //PicketLinkCoreSTS sts = PicketLinkCoreSTS.instance();
+ //sts.installDefaultConfiguration();
+ }
+
+ @Override
+ public String getId() {
+ return SamlProtocol.LOGIN_PROTOCOL;
+ }
+
+ @Override
+ public List<ProtocolMapperModel> getBuiltinMappers() {
+ return builtins;
+ }
+
+ @Override
+ public List<ProtocolMapperModel> getDefaultBuiltinMappers() {
+ return defaultBuiltins;
+ }
+
+ static List<ProtocolMapperModel> builtins = new ArrayList<>();
+ static List<ProtocolMapperModel> defaultBuiltins = new ArrayList<>();
+
+ static {
+ ProtocolMapperModel model;
+ model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 email",
+ "email",
+ X500SAMLProfileConstants.EMAIL.get(),
+ JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+ X500SAMLProfileConstants.EMAIL.getFriendlyName(),
+ true, "${email}");
+ builtins.add(model);
+ model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 givenName",
+ "firstName",
+ X500SAMLProfileConstants.GIVEN_NAME.get(),
+ JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+ X500SAMLProfileConstants.GIVEN_NAME.getFriendlyName(),
+ true, "${givenName}");
+ builtins.add(model);
+ model = UserPropertyAttributeStatementMapper.createAttributeMapper("X500 surname",
+ "lastName",
+ X500SAMLProfileConstants.SURNAME.get(),
+ JBossSAMLURIConstants.ATTRIBUTE_FORMAT_URI.get(),
+ X500SAMLProfileConstants.SURNAME.getFriendlyName(),
+ true, "${familyName}");
+ builtins.add(model);
+ model = RoleListMapper.create("role list", "Role", AttributeStatementHelper.BASIC, null, false);
+ builtins.add(model);
+ defaultBuiltins.add(model);
+
+ }
+
+
+ @Override
+ protected void addDefaults(ClientModel client) {
+ for (ProtocolMapperModel model : defaultBuiltins) {
+ model.setProtocol(getId());
+ client.addProtocolMapper(model);
+ }
+ }
+
+ @Override
+ public void setupClientDefaults(ClientRepresentation clientRep, ClientModel newClient) {
+ SamlRepresentationAttributes rep = new SamlRepresentationAttributes(clientRep.getAttributes());
+ SamlClient client = new SamlClient(newClient);
+ if (clientRep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
+ if (rep.getCanonicalizationMethod() == null) {
+ client.setCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE);
+ }
+ if (rep.getSignatureAlgorithm() == null) {
+ client.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
+ }
+
+ if (rep.getNameIDFormat() == null) {
+ client.setNameIDFormat("username");
+ }
+
+ if (rep.getIncludeAuthnStatement() == null) {
+ client.setIncludeAuthnStatement(true);
+ }
+
+ if (rep.getForceNameIDFormat() == null) {
+ client.setForceNameIDFormat(false);
+ }
+
+ if (rep.getSamlServerSignature() == null) {
+ client.setRequiresRealmSignature(true);
+ }
+ if (rep.getForcePostBinding() == null) {
+ client.setForcePostBinding(true);
+ }
+
+ if (rep.getClientSignature() == null) {
+ client.setRequiresClientSignature(true);
+ }
+
+ if (client.requiresClientSignature() && client.getClientSigningCertificate() == null) {
+ CertificateRepresentation info = KeycloakModelUtils.generateKeyPairCertificate(newClient.getClientId());
+ client.setClientSigningCertificate(info.getCertificate());
+ client.setClientSigningPrivateKey(info.getPrivateKey());
+
+ }
+
+ if (clientRep.isFrontchannelLogout() == null) {
+ newClient.setFrontchannelLogout(true);
+ }
+ }
+
+ @Override
+ public void setupTemplateDefaults(ClientTemplateRepresentation clientRep, ClientTemplateModel newClient) {
+ SamlRepresentationAttributes rep = new SamlRepresentationAttributes(clientRep.getAttributes());
+ SamlClientTemplate client = new SamlClientTemplate(newClient);
+ if (clientRep.isStandardFlowEnabled() == null) newClient.setStandardFlowEnabled(true);
+ if (rep.getCanonicalizationMethod() == null) {
+ client.setCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE);
+ }
+ if (rep.getSignatureAlgorithm() == null) {
+ client.setSignatureAlgorithm(SignatureAlgorithm.RSA_SHA256);
+ }
+
+ if (rep.getNameIDFormat() == null) {
+ client.setNameIDFormat("username");
+ }
+
+ if (rep.getIncludeAuthnStatement() == null) {
+ client.setIncludeAuthnStatement(true);
+ }
+
+ if (rep.getForceNameIDFormat() == null) {
+ client.setForceNameIDFormat(false);
+ }
+
+ if (rep.getSamlServerSignature() == null) {
+ client.setRequiresRealmSignature(true);
+ }
+ if (rep.getForcePostBinding() == null) {
+ client.setForcePostBinding(true);
+ }
+
+ if (rep.getClientSignature() == null) {
+ client.setRequiresClientSignature(true);
+ }
+
+ if (clientRep.isFrontchannelLogout() == null) {
+ newClient.setFrontchannelLogout(true);
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
new file mode 100755
index 0000000..3e03ed4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java
@@ -0,0 +1,110 @@
+package org.keycloak.protocol.saml;
+
+import org.keycloak.common.VerificationException;
+import org.keycloak.models.ClientModel;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.exceptions.ProcessingException;
+import org.keycloak.saml.processing.api.saml.v2.sig.SAML2Signature;
+import org.keycloak.saml.processing.web.util.RedirectBindingUtil;
+import org.keycloak.common.util.PemUtils;
+import org.w3c.dom.Document;
+
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlProtocolUtils {
+
+
+ public static void verifyDocumentSignature(ClientModel client, Document document) throws VerificationException {
+ SamlClient samlClient = new SamlClient(client);
+ if (!samlClient.requiresClientSignature()) {
+ return;
+ }
+ PublicKey publicKey = getSignatureValidationKey(client);
+ verifyDocumentSignature(document, publicKey);
+ }
+
+ public static void verifyDocumentSignature(Document document, PublicKey publicKey) throws VerificationException {
+ SAML2Signature saml2Signature = new SAML2Signature();
+ try {
+ if (!saml2Signature.validate(document, publicKey)) {
+ throw new VerificationException("Invalid signature on document");
+ }
+ } catch (ProcessingException e) {
+ throw new VerificationException("Error validating signature", e);
+ }
+ }
+
+ public static PublicKey getSignatureValidationKey(ClientModel client) throws VerificationException {
+ return getPublicKey(new SamlClient(client).getClientSigningCertificate());
+ }
+
+ public static PublicKey getEncryptionValidationKey(ClientModel client) throws VerificationException {
+ return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
+ }
+
+ public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
+ String certPem = client.getAttribute(attribute);
+ return getPublicKey(certPem);
+ }
+
+ private static PublicKey getPublicKey(String certPem) throws VerificationException {
+ if (certPem == null) throw new VerificationException("Client does not have a public key.");
+ Certificate cert = null;
+ try {
+ cert = PemUtils.decodeCertificate(certPem);
+ } catch (Exception e) {
+ throw new VerificationException("Could not decode cert", e);
+ }
+ return cert.getPublicKey();
+ }
+
+ public static void verifyRedirectSignature(PublicKey publicKey, UriInfo uriInformation, String paramKey) throws VerificationException {
+ MultivaluedMap<String, String> encodedParams = uriInformation.getQueryParameters(false);
+ String request = encodedParams.getFirst(paramKey);
+ String algorithm = encodedParams.getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+ String signature = encodedParams.getFirst(GeneralConstants.SAML_SIGNATURE_REQUEST_KEY);
+ String decodedAlgorithm = uriInformation.getQueryParameters(true).getFirst(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY);
+
+ if (request == null) throw new VerificationException("SAM was null");
+ if (algorithm == null) throw new VerificationException("SigAlg was null");
+ if (signature == null) throw new VerificationException("Signature was null");
+
+ // Shibboleth doesn't sign the document for redirect binding.
+ // todo maybe a flag?
+
+
+ UriBuilder builder = UriBuilder.fromPath("/")
+ .queryParam(paramKey, request);
+ if (encodedParams.containsKey(GeneralConstants.RELAY_STATE)) {
+ builder.queryParam(GeneralConstants.RELAY_STATE, encodedParams.getFirst(GeneralConstants.RELAY_STATE));
+ }
+ builder.queryParam(GeneralConstants.SAML_SIG_ALG_REQUEST_KEY, algorithm);
+ String rawQuery = builder.build().getRawQuery();
+
+ try {
+ byte[] decodedSignature = RedirectBindingUtil.urlBase64Decode(signature);
+
+ SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.getFromXmlMethod(decodedAlgorithm);
+ Signature validator = signatureAlgorithm.createSignature(); // todo plugin signature alg
+ validator.initVerify(publicKey);
+ validator.update(rawQuery.getBytes("UTF-8"));
+ if (!validator.verify(decodedSignature)) {
+ throw new VerificationException("Invalid query param signature");
+ }
+ } catch (Exception e) {
+ throw new VerificationException(e);
+ }
+ }
+
+
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
new file mode 100755
index 0000000..d5e4055
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlRepresentationAttributes.java
@@ -0,0 +1,63 @@
+package org.keycloak.protocol.saml;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlRepresentationAttributes {
+ protected Map<String, String> attributes;
+
+ public SamlRepresentationAttributes(Map<String, String> attributes) {
+ this.attributes = attributes;
+ }
+
+ public String getCanonicalizationMethod() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_CANONICALIZATION_METHOD_ATTRIBUTE);
+ }
+
+ protected Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public String getSignatureAlgorithm() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_SIGNATURE_ALGORITHM);
+ }
+
+ public String getNameIDFormat() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_NAME_ID_FORMAT_ATTRIBUTE);
+
+ }
+
+ public String getIncludeAuthnStatement() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_AUTHNSTATEMENT);
+
+ }
+
+ public String getForceNameIDFormat() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_FORCE_NAME_ID_FORMAT_ATTRIBUTE);
+ }
+
+ public String getSamlServerSignature() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_SERVER_SIGNATURE);
+
+ }
+
+ public String getForcePostBinding() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_FORCE_POST_BINDING);
+
+ }
+ public String getClientSignature() {
+ if (getAttributes() == null) return null;
+ return getAttributes().get(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE);
+
+ }
+}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
new file mode 100755
index 0000000..b598576
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -0,0 +1,566 @@
+package org.keycloak.protocol.saml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.security.PublicKey;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.FormParam;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.common.VerificationException;
+import org.keycloak.common.util.StreamUtil;
+import org.keycloak.dom.saml.v2.SAML2Object;
+import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
+import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
+import org.keycloak.dom.saml.v2.protocol.NameIDPolicyType;
+import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
+import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.AuthorizationEndpointBase;
+import org.keycloak.protocol.oidc.utils.RedirectUtils;
+import org.keycloak.protocol.saml.profile.ecp.SamlEcpProfileService;
+import org.keycloak.saml.SAML2LogoutResponseBuilder;
+import org.keycloak.saml.SAMLRequestParser;
+import org.keycloak.saml.SignatureAlgorithm;
+import org.keycloak.saml.common.constants.GeneralConstants;
+import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
+import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
+import org.keycloak.services.ErrorPage;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.RealmsResource;
+
+/**
+ * Resource class for the oauth/openid connect token service
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class SamlService extends AuthorizationEndpointBase {
+
+ protected static final Logger logger = Logger.getLogger(SamlService.class);
+
+ public SamlService(RealmModel realm, EventBuilder event) {
+ super(realm, event);
+ }
+
+ public abstract class BindingProtocol {
+ protected Response basicChecks(String samlRequest, String samlResponse) {
+ if (!checkSsl()) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.SSL_REQUIRED);
+ return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+ }
+ if (!realm.isEnabled()) {
+ event.event(EventType.LOGIN_ERROR);
+ event.error(Errors.REALM_DISABLED);
+ return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+ }
+
+ if (samlRequest == null && samlResponse == null) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.INVALID_TOKEN);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+
+ }
+ return null;
+ }
+
+ protected Response handleSamlResponse(String samlResponse, String relayState) {
+ event.event(EventType.LOGOUT);
+ SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
+ StatusResponseType statusResponse = (StatusResponseType) holder.getSamlObject();
+ // validate destination
+ if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
+ event.detail(Details.REASON, "invalid_destination");
+ event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+
+ AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
+ if (authResult == null) {
+ logger.warn("Unknown saml response.");
+ event.event(EventType.LOGOUT);
+ event.error(Errors.INVALID_TOKEN);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+ // assume this is a logout response
+ UserSessionModel userSession = authResult.getSession();
+ if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
+ logger.warn("Unknown saml response.");
+ logger.warn("UserSession is not tagged as logging out.");
+ event.event(EventType.LOGOUT);
+ event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+ logger.debug("logout response");
+ Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+ event.success();
+ return response;
+ }
+
+ protected Response handleSamlRequest(String samlRequest, String relayState) {
+ SAMLDocumentHolder documentHolder = extractRequestDocument(samlRequest);
+ if (documentHolder == null) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.INVALID_TOKEN);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+
+ SAML2Object samlObject = documentHolder.getSamlObject();
+
+ RequestAbstractType requestAbstractType = (RequestAbstractType) samlObject;
+ String issuer = requestAbstractType.getIssuer().getValue();
+ ClientModel client = realm.getClientByClientId(issuer);
+
+ if (client == null) {
+ event.event(EventType.LOGIN);
+ event.client(issuer);
+ event.error(Errors.CLIENT_NOT_FOUND);
+ return ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
+ }
+
+ if (!client.isEnabled()) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.CLIENT_DISABLED);
+ return ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+ }
+ if (client.isBearerOnly()) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.NOT_ALLOWED);
+ return ErrorPage.error(session, Messages.BEARER_ONLY);
+ }
+ if (!client.isStandardFlowEnabled()) {
+ event.event(EventType.LOGIN);
+ event.error(Errors.NOT_ALLOWED);
+ return ErrorPage.error(session, Messages.STANDARD_FLOW_DISABLED);
+ }
+
+ session.getContext().setClient(client);
+
+ try {
+ verifySignature(documentHolder, client);
+ } catch (VerificationException e) {
+ SamlService.logger.error("request validation failed", e);
+ event.event(EventType.LOGIN);
+ event.error(Errors.INVALID_SIGNATURE);
+ return ErrorPage.error(session, Messages.INVALID_REQUESTER);
+ }
+ logger.debug("verified request");
+ if (samlObject instanceof AuthnRequestType) {
+ logger.debug("** login request");
+ event.event(EventType.LOGIN);
+ // Get the SAML Request Message
+ AuthnRequestType authn = (AuthnRequestType) samlObject;
+ return loginRequest(relayState, authn, client);
+ } else if (samlObject instanceof LogoutRequestType) {
+ logger.debug("** logout request");
+ event.event(EventType.LOGOUT);
+ LogoutRequestType logout = (LogoutRequestType) samlObject;
+ return logoutRequest(logout, client, relayState);
+
+ } else {
+ event.event(EventType.LOGIN);
+ event.error(Errors.INVALID_TOKEN);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+ }
+
+ protected abstract void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException;
+
+ protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
+
+ protected abstract SAMLDocumentHolder extractResponseDocument(String response);
+
+ protected Response loginRequest(String relayState, AuthnRequestType requestAbstractType, ClientModel client) {
+ SamlClient samlClient = new SamlClient(client);
+ // validate destination
+ if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
+ event.detail(Details.REASON, "invalid_destination");
+ event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+ String bindingType = getBindingType(requestAbstractType);
+ if (samlClient.forcePostBinding())
+ bindingType = SamlProtocol.SAML_POST_BINDING;
+ String redirect = null;
+ URI redirectUri = requestAbstractType.getAssertionConsumerServiceURL();
+ if (redirectUri != null && !"null".equals(redirectUri)) { // "null" is for testing purposes
+ redirect = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri.toString(), realm, client);
+ } else {
+ if (bindingType.equals(SamlProtocol.SAML_POST_BINDING)) {
+ redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
+ } else {
+ redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
+ }
+ if (redirect == null) {
+ redirect = client.getManagementUrl();
+ }
+
+ }
+
+ if (redirect == null) {
+ event.error(Errors.INVALID_REDIRECT_URI);
+ return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
+ }
+
+ ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
+ clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
+ clientSession.setRedirectUri(redirect);
+ clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
+ clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType);
+ clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
+ clientSession.setNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
+
+ // Handle NameIDPolicy from SP
+ NameIDPolicyType nameIdPolicy = requestAbstractType.getNameIDPolicy();
+ if (nameIdPolicy != null && !samlClient.forceNameIDFormat()) {
+ String nameIdFormat = nameIdPolicy.getFormat().toString();
+ // TODO: Handle AllowCreate too, relevant for persistent NameID.
+ if (isSupportedNameIdFormat(nameIdFormat)) {
+ clientSession.setNote(GeneralConstants.NAMEID_FORMAT, nameIdFormat);
+ } else {
+ event.detail(Details.REASON, "unsupported_nameid_format");
+ event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
+ return ErrorPage.error(session, Messages.UNSUPPORTED_NAME_ID_FORMAT);
+ }
+ }
+
+ return newBrowserAuthentication(clientSession, requestAbstractType.isIsPassive());
+ }
+
+ protected String getBindingType(AuthnRequestType requestAbstractType) {
+ URI requestedProtocolBinding = requestAbstractType.getProtocolBinding();
+
+ if (requestedProtocolBinding != null) {
+ if (JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get().equals(requestedProtocolBinding.toString())) {
+ return SamlProtocol.SAML_POST_BINDING;
+ } else {
+ return SamlProtocol.SAML_REDIRECT_BINDING;
+ }
+ }
+
+ return getBindingType();
+ }
+
+ private boolean isSupportedNameIdFormat(String nameIdFormat) {
+ if (nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get()) || nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_TRANSIENT.get()) || nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get())
+ || nameIdFormat.equals(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get())) {
+ return true;
+ }
+ return false;
+ }
+
+ protected abstract String getBindingType();
+
+ protected Response logoutRequest(LogoutRequestType logoutRequest, ClientModel client, String relayState) {
+ SamlClient samlClient = new SamlClient(client);
+ // validate destination
+ if (logoutRequest.getDestination() != null && !uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
+ event.detail(Details.REASON, "invalid_destination");
+ event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
+ return ErrorPage.error(session, Messages.INVALID_REQUEST);
+ }
+
+ // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
+ AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, false);
+ if (authResult != null) {
+ String logoutBinding = getBindingType();
+ if ("true".equals(samlClient.forcePostBinding()))
+ logoutBinding = SamlProtocol.SAML_POST_BINDING;
+ String bindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
+ UserSessionModel userSession = authResult.getSession();
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING_URI, bindingUri);
+ if (samlClient.requiresRealmSignature()) {
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_SIGNATURE_ALGORITHM, samlClient.getSignatureAlgorithm().toString());
+
+ }
+ if (relayState != null)
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_RELAY_STATE, relayState);
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_REQUEST_ID, logoutRequest.getID());
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_BINDING, logoutBinding);
+ userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
+ userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
+ // remove client from logout requests
+ for (ClientSessionModel clientSession : userSession.getClientSessions()) {
+ if (clientSession.getClient().getId().equals(client.getId())) {
+ clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
+ }
+ }
+ logger.debug("browser Logout");
+ return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
+ } else if (logoutRequest.getSessionIndex() != null) {
+ for (String sessionIndex : logoutRequest.getSessionIndex()) {
+ ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
+ if (clientSession == null)
+ continue;
+ UserSessionModel userSession = clientSession.getUserSession();
+ if (clientSession.getClient().getClientId().equals(client.getClientId())) {
+ // remove requesting client from logout
+ clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
+
+ // Remove also other clientSessions of this client as there could be more in this UserSession
+ if (userSession != null) {
+ for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
+ if (clientSession2.getClient().getId().equals(client.getId())) {
+ clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
+ }
+ }
+ }
+ }
+
+ try {
+ authManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, true);
+ } catch (Exception e) {
+ logger.warn("Failure with backchannel logout", e);
+ }
+
+ }
+
+ }
+
+ // default
+
+ String logoutBinding = getBindingType();
+ String logoutBindingUri = SamlProtocol.getLogoutServiceUrl(uriInfo, client, logoutBinding);
+ String logoutRelayState = relayState;
+ SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
+ builder.logoutRequestID(logoutRequest.getID());
+ builder.destination(logoutBindingUri);
+ builder.issuer(RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+ JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder().relayState(logoutRelayState);
+ if (samlClient.requiresRealmSignature()) {
+ SignatureAlgorithm algorithm = samlClient.getSignatureAlgorithm();
+ binding.signatureAlgorithm(algorithm).signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()).signDocument();
+
+ }
+ try {
+ if (SamlProtocol.SAML_POST_BINDING.equals(logoutBinding)) {
+ return binding.postBinding(builder.buildDocument()).response(logoutBindingUri);
+ } else {
+ return binding.redirectBinding(builder.buildDocument()).response(logoutBindingUri);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean checkSsl() {
+ if (uriInfo.getBaseUri().getScheme().equals("https")) {
+ return true;
+ } else {
+ return !realm.getSslRequired().isRequired(clientConnection);
+ }
+ }
+ }
+
+ protected class PostBindingProtocol extends BindingProtocol {
+
+ @Override
+ protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
+ SamlProtocolUtils.verifyDocumentSignature(client, documentHolder.getSamlDocument());
+ }
+
+ @Override
+ protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
+ return SAMLRequestParser.parseRequestPostBinding(samlRequest);
+ }
+
+ @Override
+ protected SAMLDocumentHolder extractResponseDocument(String response) {
+ return SAMLRequestParser.parseResponsePostBinding(response);
+ }
+
+ @Override
+ protected String getBindingType() {
+ return SamlProtocol.SAML_POST_BINDING;
+ }
+
+ public Response execute(String samlRequest, String samlResponse, String relayState) {
+ Response response = basicChecks(samlRequest, samlResponse);
+ if (response != null)
+ return response;
+ if (samlRequest != null)
+ return handleSamlRequest(samlRequest, relayState);
+ else
+ return handleSamlResponse(samlResponse, relayState);
+ }
+
+ }
+
+ protected class RedirectBindingProtocol extends BindingProtocol {
+
+ @Override
+ protected void verifySignature(SAMLDocumentHolder documentHolder, ClientModel client) throws VerificationException {
+ SamlClient samlClient = new SamlClient(client);
+ if (!samlClient.requiresClientSignature()) {
+ return;
+ }
+ PublicKey publicKey = SamlProtocolUtils.getSignatureValidationKey(client);
+ SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, GeneralConstants.SAML_REQUEST_KEY);
+ }
+
+ @Override
+ protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
+ return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
+ }
+
+ @Override
+ protected SAMLDocumentHolder extractResponseDocument(String response) {
+ return SAMLRequestParser.parseRequestRedirectBinding(response);
+ }
+
+ @Override
+ protected String getBindingType() {
+ return SamlProtocol.SAML_REDIRECT_BINDING;
+ }
+
+ public Response execute(String samlRequest, String samlResponse, String relayState) {
+ Response response = basicChecks(samlRequest, samlResponse);
+ if (response != null)
+ return response;
+ if (samlRequest != null)
+ return handleSamlRequest(samlRequest, relayState);
+ else
+ return handleSamlResponse(samlResponse, relayState);
+ }
+
+ }
+
+ protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive) {
+ SamlProtocol samlProtocol = new SamlProtocol().setEventBuilder(event).setHttpHeaders(headers).setRealm(realm).setSession(session).setUriInfo(uriInfo);
+ return newBrowserAuthentication(clientSession, isPassive, samlProtocol);
+ }
+
+ protected Response newBrowserAuthentication(ClientSessionModel clientSession, boolean isPassive, SamlProtocol samlProtocol) {
+ return handleBrowserAuthenticationRequest(clientSession, samlProtocol, isPassive);
+ }
+
+ /**
+ */
+ @GET
+ public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
+ logger.debug("SAML GET");
+ return new RedirectBindingProtocol().execute(samlRequest, samlResponse, relayState);
+ }
+
+ /**
+ */
+ @POST
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
+ logger.debug("SAML POST");
+ return new PostBindingProtocol().execute(samlRequest, samlResponse, relayState);
+ }
+
+ @GET
+ @Path("descriptor")
+ @Produces(MediaType.APPLICATION_XML)
+ public String getDescriptor() throws Exception {
+ return getIDPMetadataDescriptor(uriInfo, realm);
+
+ }
+
+ public static String getIDPMetadataDescriptor(UriInfo uriInfo, RealmModel realm) throws IOException {
+ InputStream is = SamlService.class.getResourceAsStream("/idp-metadata-template.xml");
+ String template = StreamUtil.readString(is);
+ template = template.replace("${idp.entityID}", RealmsResource.realmBaseUrl(uriInfo).build(realm.getName()).toString());
+ template = template.replace("${idp.sso.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+ template = template.replace("${idp.sso.HTTP-Redirect}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+ template = template.replace("${idp.sls.HTTP-POST}", RealmsResource.protocolUrl(uriInfo).build(realm.getName(), SamlProtocol.LOGIN_PROTOCOL).toString());
+ template = template.replace("${idp.signing.certificate}", realm.getCertificatePem());
+ return template;
+ }
+
+ @GET
+ @Path("clients/{client}")
+ @Produces(MediaType.TEXT_HTML)
+ public Response idpInitiatedSSO(@PathParam("client") String clientUrlName, @QueryParam("RelayState") String relayState) {
+ event.event(EventType.LOGIN);
+ ClientModel client = null;
+ for (ClientModel c : realm.getClients()) {
+ String urlName = c.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_URL_NAME);
+ if (urlName == null)
+ continue;
+ if (urlName.equals(clientUrlName)) {
+ client = c;
+ break;
+ }
+ }
+ if (client == null) {
+ event.error(Errors.CLIENT_NOT_FOUND);
+ return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
+ }
+ if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
+ logger.error("SAML assertion consumer url not set up");
+ event.error(Errors.INVALID_REDIRECT_URI);
+ return ErrorPage.error(session, Messages.INVALID_REDIRECT_URI);
+ }
+
+ String bindingType = SamlProtocol.SAML_POST_BINDING;
+ if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) != null) {
+ bindingType = SamlProtocol.SAML_REDIRECT_BINDING;
+ }
+
+ String redirect = null;
+ if (bindingType.equals(SamlProtocol.SAML_REDIRECT_BINDING)) {
+ redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE);
+ } else {
+ redirect = client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE);
+ }
+ if (redirect == null) {
+ redirect = client.getManagementUrl();
+ }
+
+ ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
+ clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL);
+ clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret());
+ clientSession.setNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
+ clientSession.setNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
+ clientSession.setRedirectUri(redirect);
+
+ if (relayState == null) {
+ relayState = client.getAttribute(SamlProtocol.SAML_IDP_INITIATED_SSO_RELAY_STATE);
+ }
+ if (relayState != null && !relayState.trim().equals("")) {
+ clientSession.setNote(GeneralConstants.RELAY_STATE, relayState);
+ }
+
+ return newBrowserAuthentication(clientSession, false);
+
+ }
+
+ @POST
+ @Consumes({"application/soap+xml",MediaType.TEXT_XML})
+ public Response soapBinding(InputStream inputStream) {
+ SamlEcpProfileService bindingService = new SamlEcpProfileService(realm, event);
+
+ ResteasyProviderFactory.getInstance().injectProperties(bindingService);
+
+ return bindingService.authenticate(inputStream);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
new file mode 100755
index 0000000..438a277
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -0,0 +1,79 @@
+package org.keycloak.social.facebook;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.broker.social.SocialIdentityProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
+
+ public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
+ public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
+ public static final String PROFILE_URL = "https://graph.facebook.com/me?fields=id,name,email,first_name,last_name";
+ public static final String DEFAULT_SCOPE = "email";
+
+ public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
+ try {
+ JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
+
+ String id = getJsonProperty(profile, "id");
+
+ BrokeredIdentityContext user = new BrokeredIdentityContext(id);
+
+ String email = getJsonProperty(profile, "email");
+
+ user.setEmail(email);
+
+ String username = getJsonProperty(profile, "username");
+
+ if (username == null) {
+ if (email != null) {
+ username = email;
+ } else {
+ username = id;
+ }
+ }
+
+ user.setUsername(username);
+
+ String firstName = getJsonProperty(profile, "first_name");
+ String lastName = getJsonProperty(profile, "last_name");
+
+ if (lastName == null) {
+ lastName = "";
+ } else {
+ lastName = " " + lastName;
+ }
+
+ user.setName(firstName + lastName);
+ user.setIdpConfig(getConfig());
+ user.setIdp(this);
+
+ AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from facebook.", e);
+ }
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java
new file mode 100755
index 0000000..a43e560
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.facebook;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class FacebookIdentityProviderFactory extends AbstractIdentityProviderFactory<FacebookIdentityProvider> implements SocialIdentityProviderFactory<FacebookIdentityProvider> {
+
+ public static final String PROVIDER_ID = "facebook";
+
+ @Override
+ public String getName() {
+ return "Facebook";
+ }
+
+ @Override
+ public FacebookIdentityProvider create(IdentityProviderModel model) {
+ return new FacebookIdentityProvider(new OAuth2IdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/facebook/FacebookUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/facebook/FacebookUserAttributeMapper.java
new file mode 100644
index 0000000..5a49657
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/facebook/FacebookUserAttributeMapper.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.social.facebook;
+
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+
+/**
+ * User attribute mapper.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class FacebookUserAttributeMapper extends AbstractJsonUserAttributeMapper {
+
+ private static final String[] cp = new String[] { FacebookIdentityProviderFactory.PROVIDER_ID };
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return cp;
+ }
+
+ @Override
+ public String getId() {
+ return "facebook-user-attribute-mapper";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
new file mode 100755
index 0000000..a6bc116
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -0,0 +1,56 @@
+package org.keycloak.social.github;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.broker.social.SocialIdentityProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
+
+ public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
+ public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
+ public static final String PROFILE_URL = "https://api.github.com/user";
+ public static final String DEFAULT_SCOPE = "user:email";
+
+ public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
+ try {
+ JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
+
+ BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
+
+ String username = getJsonProperty(profile, "login");
+ user.setUsername(username);
+ user.setName(getJsonProperty(profile, "name"));
+ user.setEmail(getJsonProperty(profile, "email"));
+ user.setIdpConfig(getConfig());
+ user.setIdp(this);
+
+ AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from github.", e);
+ }
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java
new file mode 100755
index 0000000..8ecc73a
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.github;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class GitHubIdentityProviderFactory extends AbstractIdentityProviderFactory<GitHubIdentityProvider> implements SocialIdentityProviderFactory<GitHubIdentityProvider> {
+
+ public static final String PROVIDER_ID = "github";
+
+ @Override
+ public String getName() {
+ return "GitHub";
+ }
+
+ @Override
+ public GitHubIdentityProvider create(IdentityProviderModel model) {
+ return new GitHubIdentityProvider(new OAuth2IdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/github/GitHubUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/github/GitHubUserAttributeMapper.java
new file mode 100644
index 0000000..b4a6359
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/github/GitHubUserAttributeMapper.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.social.github;
+
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+
+/**
+ * User attribute mapper.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class GitHubUserAttributeMapper extends AbstractJsonUserAttributeMapper {
+
+ private static final String[] cp = new String[] { GitHubIdentityProviderFactory.PROVIDER_ID };
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return cp;
+ }
+
+ @Override
+ public String getId() {
+ return "github-user-attribute-mapper";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
new file mode 100755
index 0000000..b85e36d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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.social.google;
+
+import org.keycloak.broker.oidc.OIDCIdentityProvider;
+import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.broker.social.SocialIdentityProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class GoogleIdentityProvider extends OIDCIdentityProvider implements SocialIdentityProvider<OIDCIdentityProviderConfig> {
+
+ public static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
+ public static final String TOKEN_URL = "https://www.googleapis.com/oauth2/v3/token";
+ public static final String PROFILE_URL = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
+ public static final String DEFAULT_SCOPE = "openid profile email";
+
+ public GoogleIdentityProvider(OIDCIdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java
new file mode 100755
index 0000000..19f1a79
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.google;
+
+import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class GoogleIdentityProviderFactory extends AbstractIdentityProviderFactory<GoogleIdentityProvider> implements SocialIdentityProviderFactory<GoogleIdentityProvider> {
+
+ public static final String PROVIDER_ID = "google";
+
+ @Override
+ public String getName() {
+ return "Google";
+ }
+
+ @Override
+ public GoogleIdentityProvider create(IdentityProviderModel model) {
+ return new GoogleIdentityProvider(new OIDCIdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/google/GoogleUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/google/GoogleUserAttributeMapper.java
new file mode 100644
index 0000000..a2e7ef2
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/google/GoogleUserAttributeMapper.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.social.google;
+
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+
+/**
+ * User attribute mapper.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class GoogleUserAttributeMapper extends AbstractJsonUserAttributeMapper {
+
+ private static final String[] cp = new String[] { GoogleIdentityProviderFactory.PROVIDER_ID };
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return cp;
+ }
+
+ @Override
+ public String getId() {
+ return "google-user-attribute-mapper";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java
new file mode 100755
index 0000000..c898d5b
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java
@@ -0,0 +1,116 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.linkedin;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.jboss.logging.Logger;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.broker.social.SocialIdentityProvider;
+
+/**
+ * LinkedIn social provider. See https://developer.linkedin.com/docs/oauth2
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
+
+ private static final Logger log = Logger.getLogger(LinkedInIdentityProvider.class);
+
+ public static final String AUTH_URL = "https://www.linkedin.com/uas/oauth2/authorization";
+ public static final String TOKEN_URL = "https://www.linkedin.com/uas/oauth2/accessToken";
+ public static final String PROFILE_URL = "https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,public-profile-url)?format=json";
+ public static final String DEFAULT_SCOPE = "r_basicprofile r_emailaddress";
+
+ public LinkedInIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
+ log.debug("doGetFederatedIdentity()");
+ try {
+ JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL).header("Authorization", "Bearer " + accessToken));
+
+ BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
+
+ String username = extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl"));
+ user.setUsername(username);
+ user.setName(getJsonProperty(profile, "formattedName"));
+ user.setEmail(getJsonProperty(profile, "emailAddress"));
+ user.setIdpConfig(getConfig());
+ user.setIdp(this);
+
+ AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from linkedIn.", e);
+ }
+ }
+
+ protected static String extractUsernameFromProfileURL(String profileURL) {
+ if (isNotBlank(profileURL)) {
+
+ try {
+ log.debug("go to extract username from profile URL " + profileURL);
+ URL u = new URL(profileURL);
+ String path = u.getPath();
+ if (isNotBlank(path) && path.length() > 1) {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ String[] pe = path.split("/");
+ if (pe.length >= 2) {
+ return URLDecoder.decode(pe[1], "UTF-8");
+ } else {
+ log.warn("LinkedIn profile URL path is without second part: " + profileURL);
+ }
+ } else {
+ log.warn("LinkedIn profile URL is without path part: " + profileURL);
+ }
+ } catch (MalformedURLException e) {
+ log.warn("LinkedIn profile URL is malformed: " + profileURL);
+ } catch (Exception e) {
+ log.warn("LinkedIn profile URL " + profileURL + " username extraction failed due: " + e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private static boolean isNotBlank(String s) {
+ return s != null && s.trim().length() > 0;
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java
new file mode 100755
index 0000000..e39d5da
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.linkedin;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class LinkedInIdentityProviderFactory extends AbstractIdentityProviderFactory<LinkedInIdentityProvider>
+ implements SocialIdentityProviderFactory<LinkedInIdentityProvider> {
+
+ public static final String PROVIDER_ID = "linkedin";
+
+ @Override
+ public String getName() {
+ return "LinkedIn";
+ }
+
+ @Override
+ public LinkedInIdentityProvider create(IdentityProviderModel model) {
+ return new LinkedInIdentityProvider(new OAuth2IdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java
new file mode 100644
index 0000000..9bc89e7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInUserAttributeMapper.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.social.linkedin;
+
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+
+/**
+ * User attribute mapper.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class LinkedInUserAttributeMapper extends AbstractJsonUserAttributeMapper {
+
+ private static final String[] cp = new String[] { LinkedInIdentityProviderFactory.PROVIDER_ID };
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return cp;
+ }
+
+ @Override
+ public String getId() {
+ return "linkedin-user-attribute-mapper";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
new file mode 100755
index 0000000..c818e96
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java
@@ -0,0 +1,220 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.stackoverflow;
+
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.HashMap;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import org.jboss.logging.Logger;
+import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider;
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+import org.keycloak.broker.oidc.util.JsonSimpleHttp;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.broker.provider.util.SimpleHttp;
+import org.keycloak.broker.social.SocialIdentityProvider;
+
+/**
+ * Stackoverflow social provider. See https://api.stackexchange.com/docs/authentication
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvider<StackOverflowIdentityProviderConfig> implements SocialIdentityProvider<StackOverflowIdentityProviderConfig> {
+
+ private static final Logger log = Logger.getLogger(StackoverflowIdentityProvider.class);
+
+ public static final String AUTH_URL = "https://stackexchange.com/oauth";
+ public static final String TOKEN_URL = "https://stackexchange.com/oauth/access_token";
+ public static final String PROFILE_URL = "https://api.stackexchange.com/2.2/me?order=desc&sort=name&site=stackoverflow";
+ public static final String DEFAULT_SCOPE = "";
+
+ public StackoverflowIdentityProvider(StackOverflowIdentityProviderConfig config) {
+ super(config);
+ config.setAuthorizationUrl(AUTH_URL);
+ config.setTokenUrl(TOKEN_URL);
+ config.setUserInfoUrl(PROFILE_URL);
+ }
+
+ @Override
+ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
+ log.debug("doGetFederatedIdentity()");
+ try {
+
+ String URL = PROFILE_URL + "&access_token=" + accessToken + "&key=" + getConfig().getKey();
+ if (log.isDebugEnabled()) {
+ log.debug("StackOverflow profile request to: " + URL);
+ }
+ JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL)).get("items").get(0);
+
+ BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
+
+ String username = extractUsernameFromProfileURL(getJsonProperty(profile, "link"));
+ user.setUsername(username);
+ user.setName(unescapeHtml3(getJsonProperty(profile, "display_name")));
+ // email is not provided
+ // user.setEmail(getJsonProperty(profile, "email"));
+ user.setIdpConfig(getConfig());
+ user.setIdp(this);
+
+ AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
+
+ return user;
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could not obtain user profile from Stackoverflow: " + e.getMessage(), e);
+ }
+ }
+
+ protected static String extractUsernameFromProfileURL(String profileURL) {
+ if (isNotBlank(profileURL)) {
+
+ try {
+ log.debug("go to extract username from profile URL " + profileURL);
+ URL u = new URL(profileURL);
+ String path = u.getPath();
+ if (isNotBlank(path) && path.length() > 1) {
+ if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ String[] pe = path.split("/");
+ if (pe.length >= 3) {
+ return URLDecoder.decode(pe[2], "UTF-8");
+ } else {
+ log.warn("Stackoverflow profile URL path is without third part: " + profileURL);
+ }
+ } else {
+ log.warn("Stackoverflow profile URL is without path part: " + profileURL);
+ }
+ } catch (MalformedURLException e) {
+ log.warn("Stackoverflow profile URL is malformed: " + profileURL);
+ } catch (Exception e) {
+ log.warn("Stackoverflow profile URL " + profileURL + " username extraction failed due: " + e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private static boolean isNotBlank(String s) {
+ return s != null && s.trim().length() > 0;
+ }
+
+ @Override
+ protected String getDefaultScopes() {
+ return DEFAULT_SCOPE;
+ }
+
+ public static final String unescapeHtml3(final String input) {
+ if (input == null)
+ return null;
+ StringWriter writer = null;
+ int len = input.length();
+ int i = 1;
+ int st = 0;
+ while (true) {
+ // look for '&'
+ while (i < len && input.charAt(i - 1) != '&')
+ i++;
+ if (i >= len)
+ break;
+
+ // found '&', look for ';'
+ int j = i;
+ while (j < len && j < i + MAX_ESCAPE + 1 && input.charAt(j) != ';')
+ j++;
+ if (j == len || j < i + MIN_ESCAPE || j == i + MAX_ESCAPE + 1) {
+ i++;
+ continue;
+ }
+
+ // found escape
+ if (input.charAt(i) == '#') {
+ // numeric escape
+ int k = i + 1;
+ int radix = 10;
+
+ final char firstChar = input.charAt(k);
+ if (firstChar == 'x' || firstChar == 'X') {
+ k++;
+ radix = 16;
+ }
+
+ try {
+ int entityValue = Integer.parseInt(input.substring(k, j), radix);
+
+ if (writer == null)
+ writer = new StringWriter(input.length());
+ writer.append(input.substring(st, i - 1));
+
+ if (entityValue > 0xFFFF) {
+ final char[] chrs = Character.toChars(entityValue);
+ writer.write(chrs[0]);
+ writer.write(chrs[1]);
+ } else {
+ writer.write(entityValue);
+ }
+
+ } catch (NumberFormatException ex) {
+ i++;
+ continue;
+ }
+ } else {
+ // named escape
+ CharSequence value = lookupMap.get(input.substring(i, j));
+ if (value == null) {
+ i++;
+ continue;
+ }
+
+ if (writer == null)
+ writer = new StringWriter(input.length());
+ writer.append(input.substring(st, i - 1));
+
+ writer.append(value);
+ }
+
+ // skip escape
+ st = j + 1;
+ i = st;
+ }
+
+ if (writer != null) {
+ writer.append(input.substring(st, len));
+ return writer.toString();
+ }
+ return input;
+ }
+
+ private static final String[][] ESCAPES = { { "\"", "quot" }, // " - double-quote
+ { "&", "amp" }, // & - ampersand
+ { "<", "lt" }, // < - less-than
+ { ">", "gt" }, // > - greater-than
+ };
+
+ private static final int MIN_ESCAPE = 2;
+ private static final int MAX_ESCAPE = 6;
+
+ private static final HashMap<String, CharSequence> lookupMap;
+ static {
+ lookupMap = new HashMap<String, CharSequence>();
+ for (final CharSequence[] seq : ESCAPES)
+ lookupMap.put(seq[1].toString(), seq[0]);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java b/services/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java
new file mode 100644
index 0000000..f531d7c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/stackoverflow/StackOverflowIdentityProviderConfig.java
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.stackoverflow;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackOverflowIdentityProviderConfig extends OAuth2IdentityProviderConfig {
+
+ public StackOverflowIdentityProviderConfig(IdentityProviderModel model) {
+ super(model);
+ }
+
+ public String getKey() {
+ return getConfig().get("key");
+ }
+
+ public void setKey(String key) {
+ getConfig().put("key", key);
+ }
+
+}
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java
new file mode 100755
index 0000000..aa0ce76
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProviderFactory.java
@@ -0,0 +1,47 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2015 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.stackoverflow;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackoverflowIdentityProviderFactory extends
+ AbstractIdentityProviderFactory<StackoverflowIdentityProvider> implements
+ SocialIdentityProviderFactory<StackoverflowIdentityProvider> {
+
+ public static final String PROVIDER_ID = "stackoverflow";
+
+ @Override
+ public String getName() {
+ return "StackOverflow";
+ }
+
+ @Override
+ public StackoverflowIdentityProvider create(IdentityProviderModel model) {
+ return new StackoverflowIdentityProvider(new StackOverflowIdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowUserAttributeMapper.java b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowUserAttributeMapper.java
new file mode 100644
index 0000000..5fe3b97
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowUserAttributeMapper.java
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @authors tag. All rights reserved.
+ */
+package org.keycloak.social.stackoverflow;
+
+import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper;
+
+/**
+ * User attribute mapper.
+ *
+ * @author Vlastimil Elias (velias at redhat dot com)
+ */
+public class StackoverflowUserAttributeMapper extends AbstractJsonUserAttributeMapper {
+
+ private static final String[] cp = new String[] { StackoverflowIdentityProviderFactory.PROVIDER_ID };
+
+ @Override
+ public String[] getCompatibleProviders() {
+ return cp;
+ }
+
+ @Override
+ public String getId() {
+ return "stackoverflow-user-attribute-mapper";
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
new file mode 100755
index 0000000..0ddb3c0
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.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.social.twitter;
+
+import org.jboss.logging.Logger;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProvider;
+import org.keycloak.broker.provider.AuthenticationRequest;
+import org.keycloak.broker.provider.BrokeredIdentityContext;
+import org.keycloak.broker.provider.IdentityBrokerException;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.events.EventType;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.ErrorPage;
+import org.keycloak.broker.social.SocialIdentityProvider;
+import twitter4j.Twitter;
+import twitter4j.TwitterFactory;
+import twitter4j.auth.AccessToken;
+import twitter4j.auth.RequestToken;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.net.URI;
+
+import static org.keycloak.models.ClientSessionModel.Action.AUTHENTICATE;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2IdentityProviderConfig> implements
+ SocialIdentityProvider<OAuth2IdentityProviderConfig> {
+
+ protected static final Logger logger = Logger.getLogger(TwitterIdentityProvider.class);
+ public TwitterIdentityProvider(OAuth2IdentityProviderConfig config) {
+ super(config);
+ }
+
+ @Override
+ public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
+ return new Endpoint(realm, callback);
+ }
+
+ @Override
+ public Response performLogin(AuthenticationRequest request) {
+ try {
+ Twitter twitter = new TwitterFactory().getInstance();
+ twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
+
+ URI uri = new URI(request.getRedirectUri() + "?state=" + request.getState());
+
+ RequestToken requestToken = twitter.getOAuthRequestToken(uri.toString());
+ ClientSessionModel clientSession = request.getClientSession();
+
+ clientSession.setNote("twitter_token", requestToken.getToken());
+ clientSession.setNote("twitter_tokenSecret", requestToken.getTokenSecret());
+
+ URI authenticationUrl = URI.create(requestToken.getAuthenticationURL());
+
+ return Response.temporaryRedirect(authenticationUrl).build();
+ } catch (Exception e) {
+ throw new IdentityBrokerException("Could send authentication request to twitter.", e);
+ }
+ }
+
+ protected class Endpoint {
+ protected RealmModel realm;
+ protected AuthenticationCallback callback;
+
+ @Context
+ protected KeycloakSession session;
+
+ @Context
+ protected ClientConnection clientConnection;
+
+ @Context
+ protected HttpHeaders headers;
+
+ @Context
+ protected UriInfo uriInfo;
+
+ public Endpoint(RealmModel realm, AuthenticationCallback callback) {
+ this.realm = realm;
+ this.callback = callback;
+ }
+
+ @GET
+ public Response authResponse(@QueryParam("state") String state,
+ @QueryParam("denied") String denied,
+ @QueryParam("oauth_verifier") String verifier) {
+
+ try {
+ Twitter twitter = new TwitterFactory().getInstance();
+
+ twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
+
+ ClientSessionModel clientSession = parseClientSessionCode(state).getClientSession();
+
+ String twitterToken = clientSession.getNote("twitter_token");
+ String twitterSecret = clientSession.getNote("twitter_tokenSecret");
+
+ RequestToken requestToken = new RequestToken(twitterToken, twitterSecret);
+
+ AccessToken oAuthAccessToken = twitter.getOAuthAccessToken(requestToken, verifier);
+ twitter4j.User twitterUser = twitter.verifyCredentials();
+
+ BrokeredIdentityContext identity = new BrokeredIdentityContext(Long.toString(twitterUser.getId()));
+ identity.setIdp(TwitterIdentityProvider.this);
+
+ identity.setUsername(twitterUser.getScreenName());
+ identity.setName(twitterUser.getName());
+
+ StringBuilder tokenBuilder = new StringBuilder();
+
+ tokenBuilder.append("{");
+ tokenBuilder.append("\"oauth_token\":").append("\"").append(oAuthAccessToken.getToken()).append("\"").append(",");
+ tokenBuilder.append("\"oauth_token_secret\":").append("\"").append(oAuthAccessToken.getTokenSecret()).append("\"").append(",");
+ tokenBuilder.append("\"screen_name\":").append("\"").append(oAuthAccessToken.getScreenName()).append("\"").append(",");
+ tokenBuilder.append("\"user_id\":").append("\"").append(oAuthAccessToken.getUserId()).append("\"");
+ tokenBuilder.append("}");
+
+ identity.setToken(tokenBuilder.toString());
+ identity.setCode(state);
+ identity.setIdpConfig(getConfig());
+
+ return callback.authenticated(identity);
+ } catch (Exception e) {
+ logger.error("Could get user profile from twitter.", e);
+ }
+ EventBuilder event = new EventBuilder(realm, session, clientConnection);
+ event.event(EventType.LOGIN);
+ event.error("twitter_login_failed");
+ return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
+ }
+
+ private ClientSessionCode parseClientSessionCode(String code) {
+ ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realm);
+
+ if (clientCode != null && clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ ClientSessionModel clientSession = clientCode.getClientSession();
+
+ if (clientSession != null) {
+ ClientModel client = clientSession.getClient();
+
+ if (client == null) {
+ throw new IdentityBrokerException("Invalid client");
+ }
+
+ logger.debugf("Got authorization code from client [%s].", client.getClientId());
+ }
+
+ logger.debugf("Authorization code is valid.");
+
+ return clientCode;
+ }
+
+ throw new IdentityBrokerException("Invalid code, please login again through your application.");
+ }
+
+ }
+
+ @Override
+ public Response retrieveToken(FederatedIdentityModel identity) {
+ return Response.ok(identity.getToken()).type(MediaType.APPLICATION_JSON).build();
+ }
+}
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java
new file mode 100755
index 0000000..c04cf4c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.social.twitter;
+
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.broker.social.SocialIdentityProviderFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class TwitterIdentityProviderFactory extends AbstractIdentityProviderFactory<TwitterIdentityProvider> implements SocialIdentityProviderFactory<TwitterIdentityProvider> {
+
+ public static final String PROVIDER_ID = "twitter";
+
+ @Override
+ public String getName() {
+ return "Twitter";
+ }
+
+ @Override
+ public TwitterIdentityProvider create(IdentityProviderModel model) {
+ return new TwitterIdentityProvider(new OAuth2IdentityProviderConfig(model));
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/resources/META-INF/idp-metadata-template.xml b/services/src/main/resources/META-INF/idp-metadata-template.xml
new file mode 100755
index 0000000..bc667d1
--- /dev/null
+++ b/services/src/main/resources/META-INF/idp-metadata-template.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntitiesDescriptor Name="urn:keycloak"
+ xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <EntityDescriptor entityID="${idp.entityID}">
+ <IDPSSODescriptor WantAuthnRequestsSigned="true"
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
+
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="${idp.sso.HTTP-POST}" />
+ <SingleSignOnService
+ Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="${idp.sso.HTTP-Redirect}" />
+ <SingleLogoutService
+ Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="${idp.sls.HTTP-POST}" />
+ <KeyDescriptor use="signing">
+ <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
+ <dsig:X509Data>
+ <dsig:X509Certificate>
+ ${idp.signing.certificate}
+ </dsig:X509Certificate>
+ </dsig:X509Data>
+ </dsig:KeyInfo>
+ </KeyDescriptor>
+ </IDPSSODescriptor>
+ </EntityDescriptor>
+</EntitiesDescriptor>
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
index 052d1c4..a47ad08 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -14,4 +14,5 @@ org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthentic
org.keycloak.authentication.authenticators.broker.IdpConfirmLinkAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory
org.keycloak.authentication.authenticators.broker.IdpUsernamePasswordFormFactory
-org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
\ No newline at end of file
+org.keycloak.authentication.authenticators.browser.ConditionalOtpFormAuthenticatorFactory
+org.keycloak.protocol.saml.profile.ecp.authenticator.HttpBasicAuthenticator
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
index ef57994..394c776 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper
@@ -1,3 +1,15 @@
org.keycloak.broker.provider.HardcodedRoleMapper
org.keycloak.broker.provider.HardcodedAttributeMapper
-org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
\ No newline at end of file
+org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
+org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
+org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
+org.keycloak.broker.oidc.mappers.UserAttributeMapper
+org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
+org.keycloak.broker.saml.mappers.AttributeToRoleMapper
+org.keycloak.broker.saml.mappers.UserAttributeMapper
+org.keycloak.broker.saml.mappers.UsernameTemplateMapper
+org.keycloak.social.facebook.FacebookUserAttributeMapper
+org.keycloak.social.github.GitHubUserAttributeMapper
+org.keycloak.social.google.GoogleUserAttributeMapper
+org.keycloak.social.linkedin.LinkedInUserAttributeMapper
+org.keycloak.social.stackoverflow.StackoverflowUserAttributeMapper
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
new file mode 100755
index 0000000..9ba5b4d
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.broker.social.SocialIdentityProviderFactory
@@ -0,0 +1,6 @@
+org.keycloak.social.facebook.FacebookIdentityProviderFactory
+org.keycloak.social.github.GitHubIdentityProviderFactory
+org.keycloak.social.google.GoogleIdentityProviderFactory
+org.keycloak.social.linkedin.LinkedInIdentityProviderFactory
+org.keycloak.social.stackoverflow.StackoverflowIdentityProviderFactory
+org.keycloak.social.twitter.TwitterIdentityProviderFactory
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory b/services/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory
old mode 100644
new mode 100755
index 139c7b1..3f9771f
--- a/services/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.exportimport.ClientDescriptionConverterFactory
@@ -1,2 +1,3 @@
org.keycloak.exportimport.KeycloakClientDescriptionConverter
-org.keycloak.protocol.oidc.OIDCClientDescriptionConverter
\ No newline at end of file
+org.keycloak.protocol.oidc.OIDCClientDescriptionConverter
+org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
index c3da086..5f69307 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ClientInstallationProvider
@@ -1,2 +1,7 @@
org.keycloak.protocol.oidc.installation.KeycloakOIDCClientInstallation
org.keycloak.protocol.oidc.installation.KeycloakOIDCJbossSubsystemClientInstallation
+org.keycloak.protocol.saml.installation.KeycloakSamlClientInstallation
+org.keycloak.protocol.saml.installation.SamlSPDescriptorClientInstallation
+org.keycloak.protocol.saml.installation.SamlIDPDescriptorClientInstallation
+org.keycloak.protocol.saml.installation.ModAuthMellonClientInstallation
+
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory b/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
index ce23870..23d9a80 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.LoginProtocolFactory
@@ -1 +1,2 @@
-org.keycloak.protocol.oidc.OIDCLoginProtocolFactory
\ No newline at end of file
+org.keycloak.protocol.oidc.OIDCLoginProtocolFactory
+org.keycloak.protocol.saml.SamlProtocolFactory
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
index 59f0f29..14f630b 100755
--- a/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
+++ b/services/src/main/resources/META-INF/services/org.keycloak.protocol.ProtocolMapper
@@ -7,5 +7,14 @@ org.keycloak.protocol.oidc.mappers.HardcodedRole
org.keycloak.protocol.oidc.mappers.RoleNameMapper
org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper
org.keycloak.protocol.oidc.mappers.GroupMembershipMapper
+org.keycloak.protocol.saml.mappers.RoleListMapper
+org.keycloak.protocol.saml.mappers.RoleNameMapper
+org.keycloak.protocol.saml.mappers.HardcodedRole
+org.keycloak.protocol.saml.mappers.HardcodedAttributeMapper
+org.keycloak.protocol.saml.mappers.UserAttributeStatementMapper
+org.keycloak.protocol.saml.mappers.UserPropertyAttributeStatementMapper
+org.keycloak.protocol.saml.mappers.UserSessionNoteStatementMapper
+org.keycloak.protocol.saml.mappers.GroupMembershipMapper
+
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
old mode 100644
new mode 100755
index d9b8c41..5bdbca6
--- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory
@@ -1,3 +1,4 @@
org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory
org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory
-org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory
\ No newline at end of file
+org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory
+org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory
\ No newline at end of file