diff --git a/adapters/oidc/pom.xml b/adapters/oidc/pom.xml
index 91a99a54c0..ef9e79edcb 100755
--- a/adapters/oidc/pom.xml
+++ b/adapters/oidc/pom.xml
@@ -45,5 +45,6 @@
tomcatundertowwildfly
+ wildfly-elytron
diff --git a/adapters/oidc/wildfly-elytron/pom.xml b/adapters/oidc/wildfly-elytron/pom.xml
new file mode 100755
index 0000000000..5aefb70fc3
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/pom.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+ keycloak-parent
+ org.keycloak
+ 3.1.0.CR1-SNAPSHOT
+ ../../../pom.xml
+
+ 4.0.0
+
+ keycloak-wildfly-elytron-oidc-adapter
+ Keycloak Wildfly Elytron OIDC Adapter
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+ org.wildfly.common
+ wildfly-common
+ 1.2.0.Beta1
+
+
+ org.wildfly.security
+ wildfly-elytron
+ provided
+
+
+ org.wildfly.security.elytron-web
+ undertow-server
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ ${jboss.logging.version}
+ provided
+
+
+ org.keycloak
+ keycloak-core
+
+
+ org.keycloak
+ keycloak-adapter-spi
+
+
+ org.keycloak
+ keycloak-adapter-core
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ org.jboss.spec.javax.servlet
+ jboss-servlet-api_3.0_spec
+ provided
+
+
+
\ No newline at end of file
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java
new file mode 100644
index 0000000000..c8db009779
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronAccount.java
@@ -0,0 +1,103 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.wildfly.security.auth.server.SecurityIdentity;
+
+import javax.security.auth.callback.CallbackHandler;
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronAccount implements OidcKeycloakAccount {
+
+ protected static Logger log = Logger.getLogger(ElytronAccount.class);
+
+ private final KeycloakPrincipal principal;
+
+ public ElytronAccount(KeycloakPrincipal principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
+ return principal.getKeycloakSecurityContext();
+ }
+
+ @Override
+ public Principal getPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public Set getRoles() {
+ Set roles = new HashSet<>();
+
+ return roles;
+ }
+
+ void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+ principal.getKeycloakSecurityContext().setCurrentRequestInfo(deployment, tokenStore);
+ }
+
+ public boolean checkActive() {
+ RefreshableKeycloakSecurityContext session = getKeycloakSecurityContext();
+
+ if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) {
+ log.debug("session is active");
+ return true;
+ }
+
+ log.debug("session not active");
+
+ return false;
+ }
+
+ boolean tryRefresh(CallbackHandler callbackHandler) {
+ log.debug("Trying to refresh");
+
+ RefreshableKeycloakSecurityContext securityContext = getKeycloakSecurityContext();
+
+ if (securityContext == null) {
+ log.debug("No security context. Aborting refresh.");
+ }
+
+ if (securityContext.refreshExpiredToken(false)) {
+ SecurityIdentity securityIdentity = SecurityIdentityUtil.authorize(callbackHandler, principal);
+
+ if (securityIdentity != null) {
+ log.debug("refresh succeeded");
+ return true;
+ }
+
+ return false;
+ }
+
+ return checkActive();
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java
new file mode 100644
index 0000000000..eda7d17231
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronCookieTokenStore.java
@@ -0,0 +1,164 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.security.Principal;
+
+import org.jboss.logging.Logger;
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.CookieTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.Scope;
+
+import javax.security.auth.callback.CallbackHandler;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronCookieTokenStore implements ElytronTokeStore {
+
+ protected static Logger log = Logger.getLogger(ElytronCookieTokenStore.class);
+
+ private final ElytronHttpFacade httpFacade;
+ private final CallbackHandler callbackHandler;
+
+ public ElytronCookieTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
+ this.httpFacade = httpFacade;
+ this.callbackHandler = callbackHandler;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
+
+ if (principal == null) {
+ return;
+ }
+
+ RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
+
+ if (securityContext.isActive() && !securityContext.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 = securityContext.refreshExpiredToken(false);
+ if (success && securityContext.isActive()) return;
+
+ saveAccountInfo(new ElytronAccount(principal));
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, this);
+ if (principal == null) {
+ log.debug("Account was not in cookie or was invalid, returning null");
+ return false;
+ }
+ ElytronAccount account = new ElytronAccount(principal);
+
+ if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+ log.debug("Account in session belongs to a different realm than for this request.");
+ return false;
+ }
+
+ boolean active = account.checkActive();
+
+ if (!active) {
+ active = account.tryRefresh(this.callbackHandler);
+ }
+
+ if (active) {
+ log.debug("Cached account found");
+ restoreRequest();
+ httpFacade.authenticationComplete(account, true);
+ return true;
+ } else {
+ log.debug("Account was not active, removing cookie and returning false");
+ CookieTokenStore.removeCookie(httpFacade);
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ RefreshableKeycloakSecurityContext secContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
+ CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), this.httpFacade, secContext);
+ HttpScope exchange = this.httpFacade.getScope(Scope.EXCHANGE);
+
+ exchange.registerForNotification(httpServerScopes -> logout());
+
+ exchange.setAttachment(ElytronAccount.class.getName(), account);
+ exchange.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
+
+ restoreRequest();
+ }
+
+ @Override
+ public void logout() {
+ logout(false);
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ CookieTokenStore.setTokenCookie(this.httpFacade.getDeployment(), httpFacade, securityContext);
+ }
+
+ @Override
+ public void saveRequest() {
+
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return false;
+ }
+
+ @Override
+ public void logout(boolean glo) {
+ KeycloakPrincipal principal = CookieTokenStore.getPrincipalFromCookie(this.httpFacade.getDeployment(), this.httpFacade, this);
+
+ if (principal == null) {
+ return;
+ }
+
+ CookieTokenStore.removeCookie(this.httpFacade);
+
+ if (glo) {
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext) principal.getKeycloakSecurityContext();
+
+ if (ksc == null) {
+ return;
+ }
+
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+
+ if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
new file mode 100644
index 0000000000..bc2e9039ca
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronHttpFacade.java
@@ -0,0 +1,394 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.bouncycastle.asn1.cmp.Challenge;
+import org.keycloak.KeycloakSecurityContext;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AdapterTokenStore;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OIDCHttpFacade;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.enums.TokenStore;
+import org.wildfly.security.auth.server.SecurityIdentity;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.HttpServerCookie;
+import org.wildfly.security.http.HttpServerMechanismsResponder;
+import org.wildfly.security.http.HttpServerRequest;
+import org.wildfly.security.http.HttpServerResponse;
+import org.wildfly.security.http.Scope;
+
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.cert.X509Certificate;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * @author Pedro Igor
+ */
+class ElytronHttpFacade implements OIDCHttpFacade {
+
+ private final HttpServerRequest request;
+ private final CallbackHandler callbackHandler;
+ private final AdapterTokenStore tokenStore;
+ private final AdapterDeploymentContext deploymentContext;
+ private Consumer responseConsumer;
+ private ElytronAccount account;
+ private SecurityIdentity securityIdentity;
+ private boolean restored;
+
+ public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
+ this.request = request;
+ this.deploymentContext = deploymentContext;
+ this.callbackHandler = handler;
+ this.tokenStore = createTokenStore();
+ this.responseConsumer = response -> {};
+ }
+
+ void authenticationComplete(ElytronAccount account, boolean storeToken) {
+ this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, account.getPrincipal());
+
+ if (securityIdentity != null) {
+ this.account = account;
+ RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
+ account.setCurrentRequestInfo(keycloakSecurityContext.getDeployment(), this.tokenStore);
+ if (storeToken) {
+ this.tokenStore.saveAccountInfo(account);
+ }
+ }
+ }
+
+ void authenticationComplete() {
+ if (securityIdentity != null) {
+ this.request.authenticationComplete(response -> {
+ if (!restored) {
+ responseConsumer.accept(response);
+ }
+ }, () -> ((ElytronTokeStore) tokenStore).logout(true));
+ }
+ }
+
+ void authenticationFailed() {
+ this.request.authenticationFailed("Authentication Failed", response -> responseConsumer.accept(response));
+ }
+
+ void noAuthenticationInProgress() {
+ this.request.noAuthenticationInProgress();
+ }
+
+ void noAuthenticationInProgress(AuthChallenge challenge) {
+ if (challenge != null) {
+ challenge.challenge(this);
+ }
+ this.request.noAuthenticationInProgress(response -> responseConsumer.accept(response));
+ }
+
+ void authenticationInProgress() {
+ this.request.authenticationInProgress(response -> responseConsumer.accept(response));
+ }
+
+ HttpScope getScope(Scope scope) {
+ return request.getScope(scope);
+ }
+
+ HttpScope getScope(Scope scope, String id) {
+ return request.getScope(scope, id);
+ }
+
+ Collection getScopeIds(Scope scope) {
+ return request.getScopeIds(scope);
+ }
+
+ AdapterTokenStore getTokenStore() {
+ return this.tokenStore;
+ }
+
+ KeycloakDeployment getDeployment() {
+ return deploymentContext.resolveDeployment(this);
+ }
+
+ private AdapterTokenStore createTokenStore() {
+ KeycloakDeployment deployment = getDeployment();
+
+ if (TokenStore.SESSION.equals(deployment.getTokenStore())) {
+ return new ElytronSessionTokenStore(this, this.callbackHandler);
+ } else {
+ return new ElytronCookieTokenStore(this, this.callbackHandler);
+ }
+ }
+
+ @Override
+ public Request getRequest() {
+ return new Request() {
+ @Override
+ public String getMethod() {
+ return request.getRequestMethod();
+ }
+
+ @Override
+ public String getURI() {
+ try {
+ return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Failed to decode request URI", e);
+ }
+ }
+
+ @Override
+ public String getRelativePath() {
+ return request.getRequestPath();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.getRequestURI().getScheme().equals("https");
+ }
+
+ @Override
+ public String getFirstParam(String param) {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ @Override
+ public String getQueryParamValue(String param) {
+ URI requestURI = request.getRequestURI();
+ String query = requestURI.getQuery();
+ if (query != null) {
+ String[] parameters = query.split("&");
+ for (String parameter : parameters) {
+ String[] keyValue = parameter.split("=");
+ if (keyValue[0].equals(param)) {
+ return keyValue[1];
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Cookie getCookie(final String cookieName) {
+ List cookies = request.getCookies();
+
+ if (cookies != null) {
+ for (HttpServerCookie cookie : cookies) {
+ if (cookie.getName().equals(cookieName)) {
+ return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return request.getFirstRequestHeaderValue(name);
+ }
+
+ @Override
+ public List getHeaders(String name) {
+ return request.getRequestHeaderValues(name);
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return request.getInputStream();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ InetSocketAddress sourceAddress = request.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) {
+ request.getScope(Scope.EXCHANGE).setAttachment(AuthenticationError.class.getName(), error);
+ }
+
+ @Override
+ public void setError(LogoutError error) {
+ request.getScope(Scope.EXCHANGE).setAttachment(LogoutError.class.getName(), error);
+ }
+ };
+ }
+
+ @Override
+ public Response getResponse() {
+ return new Response() {
+ @Override
+ public void setStatus(final int status) {
+ responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
+ }
+
+ @Override
+ public void addHeader(final String name, final String value) {
+ responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ addHeader(name, value);
+ }
+
+ @Override
+ public void resetCookie(final String name, final String path) {
+ responseConsumer = responseConsumer.andThen(response -> setCookie(name, "", path, null, 0, false, false, response));
+ }
+
+ @Override
+ public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly) {
+ responseConsumer = responseConsumer.andThen(response -> setCookie(name, value, path, domain, maxAge, secure, httpOnly, response));
+ }
+
+ private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) {
+ response.setResponseCookie(new HttpServerCookie() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getDomain() {
+ return domain;
+ }
+
+ @Override
+ public int getMaxAge() {
+ return maxAge;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public boolean isHttpOnly() {
+ return httpOnly;
+ }
+ });
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ responseConsumer = responseConsumer.andThen(new Consumer() {
+ @Override
+ public void accept(HttpServerResponse httpServerResponse) {
+ try {
+ httpServerResponse.getOutputStream().write(stream.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write to response output stream", e);
+ }
+ }
+ });
+ return stream;
+ }
+
+ @Override
+ public void sendError(int code) {
+ setStatus(code);
+ }
+
+ @Override
+ public void sendError(final int code, final String message) {
+ responseConsumer = responseConsumer.andThen(response -> {
+ response.setStatusCode(code);
+ response.addResponseHeader("Content-Type", "text/html");
+ try {
+ response.getOutputStream().write(message.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ public void end() {
+
+ }
+ };
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain() {
+ return new X509Certificate[0];
+ }
+
+ @Override
+ public KeycloakSecurityContext getSecurityContext() {
+ if (account == null) {
+ return null;
+ }
+ return this.account.getKeycloakSecurityContext();
+ }
+
+ public boolean restoreRequest() {
+ restored = this.request.resumeRequest();
+ return restored;
+ }
+
+ public void suspendRequest() {
+ responseConsumer = responseConsumer.andThen(httpServerResponse -> request.suspendRequest());
+ }
+
+ public boolean isAuthorized() {
+ return this.securityIdentity != null;
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronRequestAuthenticator.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronRequestAuthenticator.java
new file mode 100644
index 0000000000..643a71660d
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronRequestAuthenticator.java
@@ -0,0 +1,86 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.BearerTokenRequestAuthenticator;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.OAuthRequestAuthenticator;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.Scope;
+
+import javax.security.auth.callback.CallbackHandler;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronRequestAuthenticator extends RequestAuthenticator {
+
+ public ElytronRequestAuthenticator(CallbackHandler callbackHandler, ElytronHttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort) {
+ super(facade, deployment, facade.getTokenStore(), sslRedirectPort);
+ }
+
+ @Override
+ public AuthOutcome authenticate() {
+ AuthOutcome authenticate = super.authenticate();
+
+ if (AuthOutcome.AUTHENTICATED.equals(authenticate)) {
+ if (!getElytronHttpFacade().isAuthorized()) {
+ return AuthOutcome.FAILED;
+ }
+ }
+
+ return authenticate;
+ }
+
+ @Override
+ protected OAuthRequestAuthenticator createOAuthAuthenticator() {
+ return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
+ }
+
+ @Override
+ protected void completeOAuthAuthentication(final KeycloakPrincipal principal) {
+ getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), true);
+ }
+
+ @Override
+ protected void completeBearerAuthentication(KeycloakPrincipal principal, String method) {
+ getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), false);
+ }
+
+ @Override
+ protected String changeHttpSessionId(boolean create) {
+ HttpScope session = getElytronHttpFacade().getScope(Scope.SESSION);
+
+ if (create) {
+ if (!session.exists()) {
+ session.create();
+ }
+ }
+
+ return session != null ? session.getID() : null;
+ }
+
+ private ElytronHttpFacade getElytronHttpFacade() {
+ return (ElytronHttpFacade) facade;
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
new file mode 100644
index 0000000000..385a8a6d43
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronSessionTokenStore.java
@@ -0,0 +1,202 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.util.function.Consumer;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.jboss.logging.Logger;
+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.OidcKeycloakAccount;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.HttpScopeNotification;
+import org.wildfly.security.http.Scope;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronSessionTokenStore implements ElytronTokeStore {
+
+ private static Logger log = Logger.getLogger(ElytronSessionTokenStore.class);
+
+ private final ElytronHttpFacade httpFacade;
+ private final CallbackHandler callbackHandler;
+
+ public ElytronSessionTokenStore(ElytronHttpFacade httpFacade, CallbackHandler callbackHandler) {
+ this.httpFacade = httpFacade;
+ this.callbackHandler = callbackHandler;
+ }
+
+ @Override
+ public void checkCurrentToken() {
+ HttpScope session = httpFacade.getScope(Scope.SESSION);
+ if (!session.exists()) return;
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
+ if (securityContext == null) return;
+
+ // just in case session got serialized
+ if (securityContext.getDeployment() == null) securityContext.setCurrentRequestInfo(httpFacade.getDeployment(), this);
+
+ if (securityContext.isActive() && !securityContext.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 = securityContext.refreshExpiredToken(false);
+ if (success && securityContext.isActive()) return;
+
+ // Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
+ session.setAttachment(KeycloakSecurityContext.class.getName(), null);
+ session.invalidate();
+ }
+
+ @Override
+ public boolean isCached(RequestAuthenticator authenticator) {
+ HttpScope session = this.httpFacade.getScope(Scope.SESSION);
+
+ if (session == null) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+
+ ElytronAccount account;
+
+ try {
+ account = (ElytronAccount) session.getAttachment(ElytronAccount.class.getName());
+ } catch (IllegalStateException e) {
+ log.debug("session was invalidated. Return false.");
+ return false;
+ }
+ if (account == null) {
+ log.debug("Account was not in session, returning null");
+ return false;
+ }
+
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+
+ if (!deployment.getRealm().equals(account.getKeycloakSecurityContext().getRealm())) {
+ log.debug("Account in session belongs to a different realm than for this request.");
+ return false;
+ }
+
+ boolean active = account.checkActive();
+
+ if (!active) {
+ active = account.tryRefresh(this.callbackHandler);
+ }
+
+ if (active) {
+ log.debug("Cached account found");
+ restoreRequest();
+ httpFacade.authenticationComplete(account, true);
+ return true;
+ } else {
+ log.debug("Refresh failed. Account was not active. Returning null and invalidating Http session");
+ try {
+ session.setAttachment(KeycloakSecurityContext.class.getName(), null);
+ session.setAttachment(ElytronAccount.class.getName(), null);
+ session.invalidate();
+ } catch (Exception e) {
+ log.debug("Failed to invalidate session, might already be invalidated");
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void saveAccountInfo(OidcKeycloakAccount account) {
+ HttpScope session = this.httpFacade.getScope(Scope.SESSION);
+
+ if (!session.exists()) {
+ session.create();
+ }
+
+ session.setAttachment(ElytronAccount.class.getName(), account);
+ session.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
+
+ session.registerForNotification(httpScopeNotification -> {
+ if (!httpScopeNotification.isOfType(HttpScopeNotification.SessionNotificationType.UNDEPLOY)) {
+ logout();
+ }
+ });
+
+ HttpScope scope = this.httpFacade.getScope(Scope.EXCHANGE);
+
+ scope.setAttachment(KeycloakSecurityContext.class.getName(), account.getKeycloakSecurityContext());
+ }
+
+ @Override
+ public void logout() {
+ logout(false);
+ }
+
+ @Override
+ public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
+ KeycloakPrincipal principal = new KeycloakPrincipal(AdapterUtils.getPrincipalName(this.httpFacade.getDeployment(), securityContext.getToken()), securityContext);
+ saveAccountInfo(new ElytronAccount(principal));
+ }
+
+ @Override
+ public void saveRequest() {
+ this.httpFacade.suspendRequest();
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return this.httpFacade.restoreRequest();
+ }
+
+ @Override
+ public void logout(boolean glo) {
+ HttpScope session = this.httpFacade.getScope(Scope.SESSION);
+
+ if (!session.exists()) {
+ return;
+ }
+
+ try {
+ if (glo) {
+ KeycloakSecurityContext ksc = (KeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
+
+ if (ksc == null) {
+ return;
+ }
+
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+
+ if (!deployment.isBearerOnly() && ksc != null && ksc instanceof RefreshableKeycloakSecurityContext) {
+ ((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
+ }
+ }
+
+ session.setAttachment(KeycloakSecurityContext.class.getName(), null);
+ session.setAttachment(ElytronAccount.class.getName(), null);
+ session.invalidate();
+ } catch (IllegalStateException ise) {
+ // Session may be already logged-out in case that app has adminUrl
+ log.debugf("Session %s logged-out already", session.getID());
+ }
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronTokeStore.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronTokeStore.java
new file mode 100644
index 0000000000..dc1486eb85
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/ElytronTokeStore.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.keycloak.adapters.AdapterTokenStore;
+
+/**
+ * @author Pedro Igor
+ */
+public interface ElytronTokeStore extends AdapterTokenStore {
+ void logout(boolean glo);
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakConfigurationServletListener.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakConfigurationServletListener.java
new file mode 100644
index 0000000000..ad8e9d5215
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakConfigurationServletListener.java
@@ -0,0 +1,109 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.constants.AdapterConstants;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ *
A {@link ServletContextListener} that parses the keycloak adapter configuration and set the same configuration
+ * as a {@link ServletContext} attribute in order to provide to {@link KeycloakHttpServerAuthenticationMechanism} a way
+ * to obtain the configuration when processing requests.
+ *
+ *
This listener should be automatically registered to a deployment using the subsystem.
+ *
+ * @author Pedro Igor
+ */
+public class KeycloakConfigurationServletListener implements ServletContextListener {
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ ServletContext servletContext = sce.getServletContext();
+ String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
+ KeycloakConfigResolver configResolver;
+ AdapterDeploymentContext deploymentContext;
+
+ if (configResolverClass != null) {
+ try {
+ configResolver = (KeycloakConfigResolver) servletContext.getClassLoader().loadClass(configResolverClass).newInstance();
+ deploymentContext = new AdapterDeploymentContext(configResolver);
+ } catch (Exception ex) {
+ deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
+ }
+ } else {
+ InputStream is = getConfigInputStream(servletContext);
+
+ KeycloakDeployment deployment;
+
+ if (is == null) {
+ deployment = new KeycloakDeployment();
+ } else {
+ deployment = KeycloakDeploymentBuilder.build(is);
+ }
+
+ deploymentContext = new AdapterDeploymentContext(deployment);
+ }
+
+ servletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+
+ }
+
+ 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.json");
+ } else {
+ try {
+ is = new FileInputStream(path);
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return is;
+ }
+
+ private InputStream getJSONFromServletContext(ServletContext servletContext) {
+ String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
+
+ if (json == null) {
+ return null;
+ }
+
+ return new ByteArrayInputStream(json.getBytes());
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java
new file mode 100644
index 0000000000..3fcf9bf484
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanism.java
@@ -0,0 +1,168 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.keycloak.adapters.AuthenticatedActionsHandler;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.NodesRegistrationManagement;
+import org.keycloak.adapters.PreAuthActionsHandler;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.keycloak.adapters.RequestAuthenticator;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.UserSessionManagement;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerRequest;
+import org.wildfly.security.http.Scope;
+
+/**
+ * @author Pedro Igor
+ */
+class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
+
+ static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
+ static final String NAME = "KEYCLOAK";
+
+ private final Map properties;
+ private final CallbackHandler callbackHandler;
+ private final AdapterDeploymentContext deploymentContext;
+
+ public KeycloakHttpServerAuthenticationMechanism(Map properties, CallbackHandler callbackHandler, AdapterDeploymentContext deploymentContext) {
+ this.properties = properties;
+ this.callbackHandler = callbackHandler;
+ this.deploymentContext = deploymentContext;
+ }
+
+ @Override
+ public String getMechanismName() {
+ return NAME;
+ }
+
+ @Override
+ public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
+ LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
+ AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
+
+ if (deploymentContext == null) {
+ LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
+ request.noAuthenticationInProgress();
+ return;
+ }
+
+ ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, deploymentContext, callbackHandler);
+ KeycloakDeployment deployment = httpFacade.getDeployment();
+
+ if (!deployment.isConfigured()) {
+ request.noAuthenticationInProgress();
+ return;
+ }
+
+ RequestAuthenticator authenticator = createRequestAuthenticator(request, httpFacade, deployment);
+
+ httpFacade.getTokenStore().checkCurrentToken();
+
+ if (preActions(httpFacade, deploymentContext)) {
+ LOGGER.debugf("Pre-actions has aborted the evaluation of [%s]", request.getRequestURI());
+ httpFacade.authenticationInProgress();
+ return;
+ }
+
+ AuthOutcome outcome = authenticator.authenticate();
+
+ if (AuthOutcome.AUTHENTICATED.equals(outcome)) {
+ if (new AuthenticatedActionsHandler(deployment, httpFacade).handledRequest()) {
+ httpFacade.authenticationInProgress();
+ } else {
+ httpFacade.authenticationComplete();
+ }
+ return;
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+
+ if (challenge != null) {
+ httpFacade.noAuthenticationInProgress(challenge);
+ return;
+ }
+
+ if (AuthOutcome.FAILED.equals(outcome)) {
+ httpFacade.getResponse().setStatus(403);
+ httpFacade.authenticationFailed();
+ return;
+ }
+
+ httpFacade.noAuthenticationInProgress();
+ }
+
+ private ElytronRequestAuthenticator createRequestAuthenticator(HttpServerRequest request, ElytronHttpFacade httpFacade, KeycloakDeployment deployment) {
+ return new ElytronRequestAuthenticator(this.callbackHandler, httpFacade, deployment, getConfidentialPort(request));
+ }
+
+ private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
+ if (this.deploymentContext == null) {
+ return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
+ }
+
+ return this.deploymentContext;
+ }
+
+ private boolean preActions(ElytronHttpFacade httpFacade, AdapterDeploymentContext deploymentContext) {
+ NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
+
+ nodesRegistrationManagement.tryRegister(httpFacade.getDeployment());
+
+ PreAuthActionsHandler preActions = new PreAuthActionsHandler(new UserSessionManagement() {
+ @Override
+ public void logoutAll() {
+ Collection sessions = httpFacade.getScopeIds(Scope.SESSION);
+ logoutHttpSessions(new ArrayList<>(sessions));
+ }
+
+ @Override
+ public void logoutHttpSessions(List ids) {
+ for (String id : ids) {
+ HttpScope session = httpFacade.getScope(Scope.SESSION, id);
+
+ if (session != null) {
+ session.invalidate();
+ }
+ }
+
+ }
+ }, deploymentContext, httpFacade);
+
+ return preActions.handleRequest();
+ }
+
+ // TODO: obtain confidential port from Elytron
+ private int getConfidentialPort(HttpServerRequest request) {
+ return 8443;
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java
new file mode 100644
index 0000000000..eb6b333310
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.keycloak.adapters.AdapterDeploymentContext;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
+
+import javax.security.auth.callback.CallbackHandler;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Pedro Igor
+ */
+public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
+
+ private final AdapterDeploymentContext deploymentContext;
+
+ /**
+ *
Creates a new instance.
+ *
+ *
A default constructor is necessary in order to allow this factory to be loaded via {@link java.util.ServiceLoader}.
+ */
+ public KeycloakHttpServerAuthenticationMechanismFactory() {
+ this(null);
+ }
+
+ public KeycloakHttpServerAuthenticationMechanismFactory(AdapterDeploymentContext deploymentContext) {
+ this.deploymentContext = deploymentContext;
+ }
+
+ @Override
+ public String[] getMechanismNames(Map properties) {
+ return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
+ }
+
+ @Override
+ public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
+ Map mechanismProperties = new HashMap();
+
+ mechanismProperties.putAll(properties);
+
+ if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
+ return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext);
+ }
+
+ return null;
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java
new file mode 100644
index 0000000000..6042ec82d1
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/KeycloakSecurityRealm.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.security.Principal;
+import java.util.Set;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.AdapterUtils;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.wildfly.security.auth.SupportLevel;
+import org.wildfly.security.auth.server.RealmIdentity;
+import org.wildfly.security.auth.server.RealmUnavailableException;
+import org.wildfly.security.auth.server.SecurityRealm;
+import org.wildfly.security.authz.Attributes;
+import org.wildfly.security.authz.AuthorizationIdentity;
+import org.wildfly.security.authz.MapAttributes;
+import org.wildfly.security.authz.RoleDecoder;
+import org.wildfly.security.credential.Credential;
+import org.wildfly.security.evidence.Evidence;
+
+/**
+ * @author Pedro Igor
+ */
+public class KeycloakSecurityRealm implements SecurityRealm {
+
+ @Override
+ public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
+ if (principal instanceof KeycloakPrincipal) {
+ return createRealmIdentity((KeycloakPrincipal) principal);
+ }
+ return RealmIdentity.NON_EXISTENT;
+ }
+
+ private RealmIdentity createRealmIdentity(KeycloakPrincipal principal) {
+ return new RealmIdentity() {
+ @Override
+ public Principal getRealmIdentityPrincipal() {
+ return principal;
+ }
+
+ @Override
+ public SupportLevel getCredentialAcquireSupport(Class extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
+ return SupportLevel.UNSUPPORTED;
+ }
+
+ @Override
+ public C getCredential(Class credentialType) throws RealmUnavailableException {
+ return null;
+ }
+
+ @Override
+ public SupportLevel getEvidenceVerifySupport(Class extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
+ return SupportLevel.SUPPORTED;
+ }
+
+ @Override
+ public boolean verifyEvidence(Evidence evidence) throws RealmUnavailableException {
+ return principal != null;
+ }
+
+ @Override
+ public boolean exists() throws RealmUnavailableException {
+ return principal != null;
+ }
+
+ @Override
+ public AuthorizationIdentity getAuthorizationIdentity() throws RealmUnavailableException {
+ RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) principal.getKeycloakSecurityContext();
+ Attributes attributes = new MapAttributes();
+ Set roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
+
+ attributes.addAll(RoleDecoder.KEY_ROLES, roles);
+
+ return AuthorizationIdentity.basicIdentity(attributes);
+ }
+ };
+ }
+
+ @Override
+ public SupportLevel getCredentialAcquireSupport(Class extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
+ return SupportLevel.UNSUPPORTED;
+ }
+
+ @Override
+ public SupportLevel getEvidenceVerifySupport(Class extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
+ return SupportLevel.POSSIBLY_SUPPORTED;
+ }
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/SecurityIdentityUtil.java b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/SecurityIdentityUtil.java
new file mode 100644
index 0000000000..28f6eb9094
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/java/org/keycloak/adapters/elytron/SecurityIdentityUtil.java
@@ -0,0 +1,82 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2016 Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.AuthorizeCallback;
+
+import org.keycloak.KeycloakPrincipal;
+import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
+import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
+import org.wildfly.security.auth.callback.EvidenceVerifyCallback;
+import org.wildfly.security.auth.callback.SecurityIdentityCallback;
+import org.wildfly.security.auth.server.SecurityIdentity;
+import org.wildfly.security.evidence.Evidence;
+import org.wildfly.security.http.HttpAuthenticationException;
+
+/**
+ * @author Pedro Igor
+ */
+final class SecurityIdentityUtil {
+
+ static final SecurityIdentity authorize(CallbackHandler callbackHandler, Principal principal) {
+ try {
+ EvidenceVerifyCallback evidenceVerifyCallback = new EvidenceVerifyCallback(new Evidence() {
+ @Override
+ public Principal getPrincipal() {
+ return principal;
+ }
+ });
+
+ callbackHandler.handle(new Callback[]{evidenceVerifyCallback});
+
+ if (evidenceVerifyCallback.isVerified()) {
+ AuthorizeCallback authorizeCallback = new AuthorizeCallback(null, null);
+
+ try {
+ callbackHandler.handle(new Callback[] {authorizeCallback});
+
+ authorizeCallback.isAuthorized();
+ } catch (Exception e) {
+ throw new HttpAuthenticationException(e);
+ }
+
+ SecurityIdentityCallback securityIdentityCallback = new SecurityIdentityCallback();
+
+ callbackHandler.handle(new Callback[]{AuthenticationCompleteCallback.SUCCEEDED, securityIdentityCallback});
+
+ SecurityIdentity securityIdentity = securityIdentityCallback.getSecurityIdentity();
+
+ return securityIdentity;
+ }
+ } catch (UnsupportedCallbackException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return null;
+ }
+
+}
diff --git a/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/org.wildfly.security.http.HttpServerAuthenticationMechanismFactory b/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/org.wildfly.security.http.HttpServerAuthenticationMechanismFactory
new file mode 100644
index 0000000000..96a0441f32
--- /dev/null
+++ b/adapters/oidc/wildfly-elytron/src/main/resources/META-INF/services/org.wildfly.security.http.HttpServerAuthenticationMechanismFactory
@@ -0,0 +1,19 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2016 Red Hat, Inc., and individual contributors
+# as indicated by the @author tags.
+#
+# 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.
+#
+
+org.keycloak.adapters.elytron.KeycloakHttpServerAuthenticationMechanismFactory
\ No newline at end of file
diff --git a/adapters/oidc/wildfly/wildfly-adapter/pom.xml b/adapters/oidc/wildfly/wildfly-adapter/pom.xml
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
diff --git a/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java b/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
index f9a1e77c29..280c3fe459 100755
--- a/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
+++ b/adapters/saml/core-public/src/main/java/org/keycloak/adapters/saml/SamlPrincipal.java
@@ -24,6 +24,7 @@ import java.io.Serializable;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -31,6 +32,9 @@ import java.util.Set;
* @version $Revision: 1 $
*/
public class SamlPrincipal implements Serializable, Principal {
+
+ public static final String DEFAULT_ROLE_ATTRIBUTE_NAME = "Roles";
+
private MultivaluedHashMap attributes = new MultivaluedHashMap<>();
private MultivaluedHashMap friendlyAttributes = new MultivaluedHashMap<>();
private String name;
@@ -98,6 +102,15 @@ public class SamlPrincipal implements Serializable, Principal {
}
+ /**
+ * Convenience function that gets the attributes associated with this principal
+ *
+ * @return attributes associated with this principal
+ */
+ public Map> getAttributes() {
+ return Collections.unmodifiableMap(attributes);
+ }
+
/**
* Convenience function that gets Attribute value by attribute friendly name
*
diff --git a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
index cb9b4d9fd7..550eeeb616 100644
--- a/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
+++ b/adapters/saml/core/src/main/java/org/keycloak/adapters/saml/profile/AbstractSamlAuthenticationHandler.java
@@ -17,6 +17,8 @@
package org.keycloak.adapters.saml.profile;
+import static org.keycloak.adapters.saml.SamlPrincipal.DEFAULT_ROLE_ATTRIBUTE_NAME;
+
import org.jboss.logging.Logger;
import org.keycloak.adapters.saml.AbstractInitiateLogin;
import org.keycloak.adapters.saml.OnSessionCreated;
@@ -422,6 +424,11 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
}
}
}
+
+ // roles should also be there as regular attributes
+ // this mainly required for elytron and its ABAC nature
+ attributes.put(DEFAULT_ROLE_ATTRIBUTE_NAME, new ArrayList<>(roles));
+
if (deployment.getPrincipalNamePolicy() == SamlDeployment.PrincipalNamePolicy.FROM_ATTRIBUTE) {
if (deployment.getPrincipalAttributeName() != null) {
String attribute = attributes.getFirst(deployment.getPrincipalAttributeName());
diff --git a/adapters/saml/pom.xml b/adapters/saml/pom.xml
index 614646d37a..7ca4c17a05 100755
--- a/adapters/saml/pom.xml
+++ b/adapters/saml/pom.xml
@@ -39,5 +39,6 @@
wildflyas7-eap6servlet-filter
+ wildfly-elytron
diff --git a/adapters/saml/wildfly-elytron/pom.xml b/adapters/saml/wildfly-elytron/pom.xml
new file mode 100755
index 0000000000..51af38019b
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/pom.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+ keycloak-parent
+ org.keycloak
+ 3.1.0.CR1-SNAPSHOT
+ ../../../pom.xml
+
+ 4.0.0
+
+ keycloak-saml-wildfly-elytron-adapter
+ Keycloak WildFly Elytron SAML Adapter
+
+
+
+ 1.8
+ 1.8
+
+
+
+
+ org.keycloak
+ keycloak-adapter-core
+ provided
+
+
+ org.keycloak
+ keycloak-saml-core
+ provided
+
+
+ org.keycloak
+ keycloak-adapter-spi
+ provided
+
+
+ org.keycloak
+ keycloak-common
+ provided
+
+
+ org.keycloak
+ keycloak-saml-adapter-api-public
+ provided
+
+
+ org.keycloak
+ keycloak-saml-adapter-core
+ provided
+
+
+ org.jboss.logging
+ jboss-logging
+ provided
+
+
+ org.jboss.spec.javax.servlet
+ jboss-servlet-api_3.0_spec
+ provided
+
+
+ org.wildfly.security
+ wildfly-elytron
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ ${maven.compiler.target}
+
+
+
+
+
+
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java
new file mode 100644
index 0000000000..88e96f8bd3
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronHttpFacade.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.cert.X509Certificate;
+
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.saml.SamlSession;
+import org.keycloak.adapters.saml.SamlSessionStore;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthenticationError;
+import org.keycloak.adapters.spi.HttpFacade;
+import org.keycloak.adapters.spi.LogoutError;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.wildfly.security.auth.callback.AnonymousAuthorizationCallback;
+import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
+import org.wildfly.security.auth.callback.SecurityIdentityCallback;
+import org.wildfly.security.auth.server.SecurityIdentity;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.HttpServerCookie;
+import org.wildfly.security.http.HttpServerMechanismsResponder;
+import org.wildfly.security.http.HttpServerRequest;
+import org.wildfly.security.http.HttpServerResponse;
+import org.wildfly.security.http.Scope;
+
+/**
+ * @author Pedro Igor
+ */
+class ElytronHttpFacade implements HttpFacade {
+
+ private final HttpServerRequest request;
+ private final CallbackHandler callbackHandler;
+ private final SamlDeploymentContext deploymentContext;
+ private final SamlSessionStore sessionStore;
+ private Consumer responseConsumer;
+ private SecurityIdentity securityIdentity;
+ private boolean restored;
+ private SamlSession samlSession;
+
+ public ElytronHttpFacade(HttpServerRequest request, SessionIdMapper idMapper, SamlDeploymentContext deploymentContext, CallbackHandler handler) {
+ this.request = request;
+ this.deploymentContext = deploymentContext;
+ this.callbackHandler = handler;
+ this.responseConsumer = response -> {};
+ this.sessionStore = createTokenStore(idMapper);
+ }
+
+ private SamlSessionStore createTokenStore(SessionIdMapper idMapper) {
+ return new ElytronSamlSessionStore(this, idMapper, getDeployment());
+ }
+
+ void authenticationComplete(SamlSession samlSession) {
+ this.samlSession = samlSession;
+ }
+
+ void authenticationComplete() {
+ this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, samlSession.getPrincipal());
+ this.request.authenticationComplete(response -> {
+ if (!restored) {
+ responseConsumer.accept(response);
+ }
+ }, () -> ((ElytronTokeStore) sessionStore).logout(true));
+ }
+
+ void authenticationCompleteAnonymous() {
+ try {
+ AnonymousAuthorizationCallback anonymousAuthorizationCallback = new AnonymousAuthorizationCallback(null);
+
+ callbackHandler.handle(new Callback[]{anonymousAuthorizationCallback});
+
+ if (anonymousAuthorizationCallback.isAuthorized()) {
+ callbackHandler.handle(new Callback[]{AuthenticationCompleteCallback.SUCCEEDED, new SecurityIdentityCallback()});
+ }
+
+ request.authenticationComplete(response -> response.forward(getRequest().getRelativePath()));
+ } catch (Exception e) {
+ throw new RuntimeException("Unexpected error processing callbacks during logout.", e);
+ }
+ }
+
+ void authenticationFailed() {
+ this.request.authenticationFailed("Authentication Failed", response -> responseConsumer.accept(response));
+ }
+
+ void noAuthenticationInProgress(AuthChallenge challenge) {
+ if (challenge != null) {
+ challenge.challenge(this);
+ }
+ this.request.noAuthenticationInProgress(response -> responseConsumer.accept(response));
+ }
+
+ void authenticationInProgress() {
+ this.request.authenticationInProgress(response -> responseConsumer.accept(response));
+ }
+
+ HttpScope getScope(Scope scope) {
+ return request.getScope(scope);
+ }
+
+ HttpScope getScope(Scope scope, String id) {
+ return request.getScope(scope, id);
+ }
+
+ Collection getScopeIds(Scope scope) {
+ return request.getScopeIds(scope);
+ }
+
+ SamlDeployment getDeployment() {
+ return deploymentContext.resolveDeployment(this);
+ }
+
+ @Override
+ public Request getRequest() {
+ return new Request() {
+ @Override
+ public String getMethod() {
+ return request.getRequestMethod();
+ }
+
+ @Override
+ public String getURI() {
+ try {
+ return URLDecoder.decode(request.getRequestURI().toString(), "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("Failed to decode request URI", e);
+ }
+ }
+
+ @Override
+ public String getRelativePath() {
+ return request.getRequestPath();
+ }
+
+ @Override
+ public boolean isSecure() {
+ return request.getRequestURI().getScheme().equals("https");
+ }
+
+ @Override
+ public String getFirstParam(String param) {
+ return request.getFirstParameterValue(param);
+ }
+
+ @Override
+ public String getQueryParamValue(String param) {
+ return request.getFirstParameterValue(param);
+ }
+
+ @Override
+ public Cookie getCookie(final String cookieName) {
+ List cookies = request.getCookies();
+
+ if (cookies != null) {
+ for (HttpServerCookie cookie : cookies) {
+ if (cookie.getName().equals(cookieName)) {
+ return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getHeader(String name) {
+ return request.getFirstRequestHeaderValue(name);
+ }
+
+ @Override
+ public List getHeaders(String name) {
+ return request.getRequestHeaderValues(name);
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return request.getInputStream();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ InetSocketAddress sourceAddress = request.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) {
+ request.getScope(Scope.EXCHANGE).setAttachment(AuthenticationError.class.getName(), error);
+ }
+
+ @Override
+ public void setError(LogoutError error) {
+ request.getScope(Scope.EXCHANGE).setAttachment(LogoutError.class.getName(), error);
+ }
+ };
+ }
+
+ @Override
+ public Response getResponse() {
+ return new Response() {
+ @Override
+ public void setStatus(final int status) {
+ responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
+ }
+
+ @Override
+ public void addHeader(final String name, final String value) {
+ responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ addHeader(name, value);
+ }
+
+ @Override
+ public void resetCookie(final String name, final String path) {
+ responseConsumer = responseConsumer.andThen(response -> setCookie(name, "", path, null, 0, false, false, response));
+ }
+
+ @Override
+ public void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly) {
+ responseConsumer = responseConsumer.andThen(response -> setCookie(name, value, path, domain, maxAge, secure, httpOnly, response));
+ }
+
+ private void setCookie(final String name, final String value, final String path, final String domain, final int maxAge, final boolean secure, final boolean httpOnly, HttpServerResponse response) {
+ response.setResponseCookie(new HttpServerCookie() {
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String getDomain() {
+ return domain;
+ }
+
+ @Override
+ public int getMaxAge() {
+ return maxAge;
+ }
+
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean isSecure() {
+ return secure;
+ }
+
+ @Override
+ public int getVersion() {
+ return 0;
+ }
+
+ @Override
+ public boolean isHttpOnly() {
+ return httpOnly;
+ }
+ });
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ responseConsumer = responseConsumer.andThen(new Consumer() {
+ @Override
+ public void accept(HttpServerResponse httpServerResponse) {
+ try {
+ httpServerResponse.getOutputStream().write(stream.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to write to response output stream", e);
+ }
+ }
+ });
+ return stream;
+ }
+
+ @Override
+ public void sendError(int code) {
+ setStatus(code);
+ }
+
+ @Override
+ public void sendError(final int code, final String message) {
+ responseConsumer = responseConsumer.andThen(response -> {
+ response.setStatusCode(code);
+ response.addResponseHeader("Content-Type", "text/html");
+ try {
+ response.getOutputStream().write(message.getBytes());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+ @Override
+ public void end() {
+
+ }
+ };
+ }
+
+ @Override
+ public X509Certificate[] getCertificateChain() {
+ return new X509Certificate[0];
+ }
+
+ public boolean restoreRequest() {
+ restored = this.request.resumeRequest();
+ return restored;
+ }
+
+ public void suspendRequest() {
+ responseConsumer = responseConsumer.andThen(httpServerResponse -> request.suspendRequest());
+ }
+
+ public boolean isAuthorized() {
+ return this.securityIdentity != null;
+ }
+
+ public URI getURI() {
+ return request.getRequestURI();
+ }
+
+ public SamlSessionStore getSessionStore() {
+ return sessionStore;
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlAuthenticator.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlAuthenticator.java
new file mode 100644
index 0000000000..29975edc9c
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlAuthenticator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import javax.security.auth.callback.CallbackHandler;
+
+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 org.keycloak.adapters.saml.profile.SamlAuthenticationHandler;
+import org.keycloak.adapters.saml.profile.webbrowsersso.BrowserHandler;
+import org.keycloak.adapters.spi.HttpFacade;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronSamlAuthenticator extends SamlAuthenticator {
+ private final CallbackHandler callbackHandler;
+ private final ElytronHttpFacade facade;
+
+ public ElytronSamlAuthenticator(ElytronHttpFacade facade, SamlDeployment samlDeployment, CallbackHandler callbackHandler) {
+ super(facade, samlDeployment, facade.getSessionStore());
+ this.callbackHandler = callbackHandler;
+ this.facade = facade;
+ }
+
+ @Override
+ protected void completeAuthentication(SamlSession samlSession) {
+ facade.authenticationComplete(samlSession);
+ }
+
+ @Override
+ protected SamlAuthenticationHandler createBrowserHandler(HttpFacade facade, SamlDeployment deployment, SamlSessionStore sessionStore) {
+ return new BrowserHandler(facade, deployment, sessionStore);
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlEndpoint.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlEndpoint.java
new file mode 100644
index 0000000000..17997e5273
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlEndpoint.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlSession;
+
+/**
+ * @author Pedro Igor
+ */
+public class ElytronSamlEndpoint extends SamlAuthenticator {
+
+ private final ElytronHttpFacade facade;
+
+ public ElytronSamlEndpoint(ElytronHttpFacade facade, SamlDeployment samlDeployment) {
+ super(facade, samlDeployment, facade.getSessionStore());
+ this.facade = facade;
+ }
+
+ @Override
+ protected void completeAuthentication(SamlSession samlSession) {
+ facade.authenticationComplete(samlSession);
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlSessionStore.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlSessionStore.java
new file mode 100644
index 0000000000..2ce62928df
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronSamlSessionStore.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.net.URI;
+import java.security.Principal;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import org.jboss.logging.Logger;
+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.spi.SessionIdMapper;
+import org.keycloak.common.util.KeycloakUriBuilder;
+import org.wildfly.security.http.HttpScope;
+import org.wildfly.security.http.Scope;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class ElytronSamlSessionStore implements SamlSessionStore, ElytronTokeStore {
+ protected static Logger log = Logger.getLogger(SamlSessionStore.class);
+ public static final String SAML_REDIRECT_URI = "SAML_REDIRECT_URI";
+
+ private final SessionIdMapper idMapper;
+ protected final SamlDeployment deployment;
+ private final ElytronHttpFacade exchange;
+
+
+ public ElytronSamlSessionStore(ElytronHttpFacade exchange, SessionIdMapper idMapper, SamlDeployment deployment) {
+ this.exchange = exchange;
+ this.idMapper = idMapper;
+ this.deployment = deployment;
+ }
+
+ @Override
+ public void setCurrentAction(CurrentAction action) {
+ if (action == CurrentAction.NONE && !exchange.getScope(Scope.SESSION).exists()) return;
+ exchange.getScope(Scope.SESSION).setAttachment(CURRENT_ACTION, action);
+ }
+
+ @Override
+ public boolean isLoggingIn() {
+ HttpScope session = exchange.getScope(Scope.SESSION);
+ if (!session.exists()) return false;
+ CurrentAction action = (CurrentAction) session.getAttachment(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_IN;
+ }
+
+ @Override
+ public boolean isLoggingOut() {
+ HttpScope session = exchange.getScope(Scope.SESSION);
+ if (!session.exists()) return false;
+ CurrentAction action = (CurrentAction) session.getAttachment(CURRENT_ACTION);
+ return action == CurrentAction.LOGGING_OUT;
+ }
+
+ @Override
+ public void logoutAccount() {
+ HttpScope session = getSession(false);
+ if (session.exists()) {
+ SamlSession samlSession = (SamlSession)session.getAttachment(SamlSession.class.getName());
+ if (samlSession != null) {
+ if (samlSession.getSessionIndex() != null) {
+ idMapper.removeSession(session.getID());
+ }
+ session.setAttachment(SamlSession.class.getName(), null);
+ }
+ session.setAttachment(SAML_REDIRECT_URI, null);
+ }
+ }
+
+ @Override
+ public void logoutByPrincipal(String principal) {
+ Set sessions = idMapper.getUserSessions(principal);
+ if (sessions != null) {
+ List ids = new LinkedList<>();
+ ids.addAll(sessions);
+ logoutSessionIds(ids);
+ for (String id : ids) {
+ idMapper.removeSession(id);
+ }
+ }
+
+ }
+
+ @Override
+ public void logoutBySsoId(List ssoIds) {
+ if (ssoIds == null) return;
+ List 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 sessionIds) {
+ sessionIds.forEach(id -> {
+ HttpScope scope = exchange.getScope(Scope.SESSION, id);
+
+ if (scope.exists()) {
+ scope.invalidate();
+ }
+ });
+ }
+
+ @Override
+ public boolean isLoggedIn() {
+ HttpScope session = getSession(false);
+ if (!session.exists()) {
+ log.debug("session was null, returning null");
+ return false;
+ }
+ final SamlSession samlSession = (SamlSession)session.getAttachment(SamlSession.class.getName());
+ if (samlSession == null) {
+ log.debug("SamlSession was not in session, returning null");
+ return false;
+ }
+
+ exchange.authenticationComplete(samlSession);
+ restoreRequest();
+ return true;
+ }
+
+ @Override
+ public void saveAccount(SamlSession account) {
+ HttpScope session = getSession(true);
+ session.setAttachment(SamlSession.class.getName(), account);
+ String sessionId = changeSessionId(session);
+ idMapper.map(account.getSessionIndex(), account.getPrincipal().getSamlSubject(), sessionId);
+
+ }
+
+ protected String changeSessionId(HttpScope session) {
+ if (!deployment.turnOffChangeSessionIdOnLogin()) return session.getID();
+ else return session.getID();
+ }
+
+ @Override
+ public SamlSession getAccount() {
+ HttpScope session = getSession(true);
+ return (SamlSession)session.getAttachment(SamlSession.class.getName());
+ }
+
+ @Override
+ public String getRedirectUri() {
+ HttpScope session = exchange.getScope(Scope.SESSION);
+ String redirect = (String) session.getAttachment(SAML_REDIRECT_URI);
+ if (redirect == null) {
+ URI uri = exchange.getURI();
+ String path = uri.getPath();
+ String relativePath = exchange.getRequest().getRelativePath();
+ String contextPath = path.substring(0, path.indexOf(relativePath));
+
+ if (!contextPath.isEmpty()) {
+ contextPath = contextPath + "/";
+ }
+
+ String baseUri = KeycloakUriBuilder.fromUri(path).replacePath(contextPath).build().toString();
+ return SamlUtil.getRedirectTo(exchange, contextPath, baseUri);
+ }
+ return redirect;
+ }
+
+ @Override
+ public void saveRequest() {
+ exchange.suspendRequest();
+ HttpScope scope = exchange.getScope(Scope.SESSION);
+
+ if (!scope.exists()) {
+ scope.create();
+ }
+
+ KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getURI()).replaceQuery(exchange.getURI().getQuery());
+ String uri = uriBuilder.build().toString();
+
+ scope.setAttachment(SAML_REDIRECT_URI, uri);
+ }
+
+ @Override
+ public boolean restoreRequest() {
+ return exchange.restoreRequest();
+ }
+
+ protected HttpScope getSession(boolean create) {
+ HttpScope scope = exchange.getScope(Scope.SESSION);
+
+ if (!scope.exists() && create) {
+ scope.create();
+ }
+
+ return scope;
+ }
+
+ @Override
+ public void logout(boolean glo) {
+ logoutAccount();
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronTokeStore.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronTokeStore.java
new file mode 100644
index 0000000000..a658464d3a
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/ElytronTokeStore.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+/**
+ * @author Pedro Igor
+ */
+public interface ElytronTokeStore {
+ void logout(boolean glo);
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakConfigurationServletListener.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakConfigurationServletListener.java
new file mode 100644
index 0000000000..94ae5920d3
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakConfigurationServletListener.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.AdapterConstants;
+import org.keycloak.adapters.saml.DefaultSamlDeployment;
+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.saml.common.exceptions.ParsingException;
+
+/**
+ *
A {@link ServletContextListener} that parses the keycloak adapter configuration and set the same configuration
+ * as a {@link ServletContext} attribute in order to provide to {@link KeycloakHttpServerAuthenticationMechanism} a way
+ * to obtain the configuration when processing requests.
+ *
+ *
This listener should be automatically registered to a deployment using the subsystem.
+ *
+ * @author Pedro Igor
+ */
+public class KeycloakConfigurationServletListener implements ServletContextListener {
+
+ protected static Logger log = Logger.getLogger(KeycloakConfigurationServletListener.class);
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ ServletContext servletContext = sce.getServletContext();
+ 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);
+ servletContext.setAttribute(SamlDeploymentContext.class.getName(), deploymentContext);
+ log.debug("Keycloak is using a per-deployment configuration.");
+ }
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+
+ }
+
+ 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;
+ }
+
+ 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());
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java
new file mode 100644
index 0000000000..9fce501d93
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanism.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.net.URI;
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.jboss.logging.Logger;
+import org.keycloak.adapters.saml.SamlAuthenticator;
+import org.keycloak.adapters.saml.SamlDeployment;
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.spi.AuthChallenge;
+import org.keycloak.adapters.spi.AuthOutcome;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerRequest;
+import org.wildfly.security.http.Scope;
+
+/**
+ * @author Pedro Igor
+ */
+class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
+
+ static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
+ static final String NAME = "KEYCLOAK-SAML";
+
+ private final Map properties;
+ private final CallbackHandler callbackHandler;
+ private final SamlDeploymentContext deploymentContext;
+ private final SessionIdMapper idMapper;
+
+ public KeycloakHttpServerAuthenticationMechanism(Map properties, CallbackHandler callbackHandler, SamlDeploymentContext deploymentContext, SessionIdMapper idMapper) {
+ this.properties = properties;
+ this.callbackHandler = callbackHandler;
+ this.deploymentContext = deploymentContext;
+ this.idMapper = idMapper;
+ }
+
+ @Override
+ public String getMechanismName() {
+ return NAME;
+ }
+
+ @Override
+ public void evaluateRequest(HttpServerRequest request) throws HttpAuthenticationException {
+ LOGGER.debugf("Evaluating request for path [%s]", request.getRequestURI());
+ SamlDeploymentContext deploymentContext = getDeploymentContext(request);
+
+ if (deploymentContext == null) {
+ LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
+ request.noAuthenticationInProgress();
+ return;
+ }
+
+ ElytronHttpFacade httpFacade = new ElytronHttpFacade(request, idMapper, deploymentContext, callbackHandler);
+ SamlDeployment deployment = httpFacade.getDeployment();
+
+ if (!deployment.isConfigured()) {
+ request.noAuthenticationInProgress();
+ return;
+ }
+
+ if (httpFacade.getRequest().getRelativePath().contains(deployment.getLogoutPage())) {
+ LOGGER.debugf("Ignoring request for [%s] and logout page [%s].", request.getRequestURI(), deployment.getLogoutPage());
+ httpFacade.authenticationCompleteAnonymous();
+ return;
+ }
+
+ SamlAuthenticator authenticator;
+
+ if (httpFacade.getRequest().getRelativePath().endsWith("/saml")) {
+ authenticator = new ElytronSamlEndpoint(httpFacade, deployment);
+ } else {
+ authenticator = new ElytronSamlAuthenticator(httpFacade, deployment, callbackHandler);
+
+ }
+
+ AuthOutcome outcome = authenticator.authenticate();
+
+ if (outcome == AuthOutcome.AUTHENTICATED) {
+ httpFacade.authenticationComplete();
+ return;
+ }
+
+ if (outcome == AuthOutcome.NOT_AUTHENTICATED) {
+ httpFacade.noAuthenticationInProgress(null);
+ return;
+ }
+
+ if (outcome == AuthOutcome.LOGGED_OUT) {
+ if (deployment.getLogoutPage() != null) {
+ redirectLogout(deployment, httpFacade);
+ }
+ httpFacade.authenticationInProgress();
+ return;
+ }
+
+ AuthChallenge challenge = authenticator.getChallenge();
+
+ if (challenge != null) {
+ httpFacade.noAuthenticationInProgress(challenge);
+ return;
+ }
+
+ if (outcome == AuthOutcome.FAILED) {
+ httpFacade.authenticationFailed();
+ return;
+ }
+
+ httpFacade.authenticationInProgress();
+ }
+
+ private SamlDeploymentContext getDeploymentContext(HttpServerRequest request) {
+ if (this.deploymentContext == null) {
+ return (SamlDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(SamlDeploymentContext.class.getName());
+ }
+
+ return this.deploymentContext;
+ }
+
+ protected void redirectLogout(SamlDeployment deployment, ElytronHttpFacade exchange) {
+ String page = deployment.getLogoutPage();
+ sendRedirect(exchange, page);
+ exchange.getResponse().setStatus(302);
+ }
+
+ static void sendRedirect(final ElytronHttpFacade exchange, final String location) {
+ // TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better
+ // handle this.
+ URI uri = exchange.getURI();
+ String path = uri.getPath();
+ String relativePath = exchange.getRequest().getRelativePath();
+ String contextPath = path.substring(0, path.indexOf(relativePath));
+ String loc = exchange.getURI().getScheme() + "://" + exchange.getURI().getHost() + ":" + exchange.getURI().getPort() + contextPath + location;
+ exchange.getResponse().setHeader("Location", loc);
+ }
+}
diff --git a/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java
new file mode 100644
index 0000000000..c1b69a4435
--- /dev/null
+++ b/adapters/saml/wildfly-elytron/src/main/java/org/keycloak/adapters/saml/elytron/KeycloakHttpServerAuthenticationMechanismFactory.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * 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.elytron;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.callback.CallbackHandler;
+
+import org.keycloak.adapters.saml.SamlDeploymentContext;
+import org.keycloak.adapters.spi.InMemorySessionIdMapper;
+import org.keycloak.adapters.spi.SessionIdMapper;
+import org.wildfly.security.http.HttpAuthenticationException;
+import org.wildfly.security.http.HttpServerAuthenticationMechanism;
+import org.wildfly.security.http.HttpServerAuthenticationMechanismFactory;
+
+/**
+ * @author Pedro Igor
+ */
+public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
+
+ private SessionIdMapper idMapper = new InMemorySessionIdMapper();
+ private final SamlDeploymentContext deploymentContext;
+
+ /**
+ *