Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2017-04-06 10:44:43 -04:00
commit 201d2c6aac
369 changed files with 66132 additions and 853 deletions

View file

@ -88,7 +88,6 @@
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
@ -104,7 +103,6 @@ projects that depend on this project.-->
<dependency>
<groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency>
<dependency>

View file

@ -30,6 +30,18 @@
<artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>as7-adapter-spi</module>
<module>as7-adapter</module>

View file

@ -33,9 +33,22 @@
<modules>
<module>jetty-core</module>
<module>jetty8.1</module>
<module>jetty9.1</module>
<module>jetty9.2</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty9.1</module>
<module>jetty9.3</module>
<module>jetty9.4</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -832,11 +832,16 @@
document.body.appendChild(iframe);
var messageCallback = function(event) {
if (event.origin !== loginIframe.iframeOrigin) {
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
return;
}
if (event.data != "unchanged") {
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
return;
}
if (event.data != 'unchanged') {
kc.clearToken();
}
@ -844,7 +849,7 @@
for (var i = callbacks.length - 1; i >= 0; --i) {
var promise = callbacks[i];
if (event.data == "unchanged") {
if (event.data == 'unchanged') {
promise.setSuccess();
} else {
promise.setError();

View file

@ -45,5 +45,6 @@
<module>tomcat</module>
<module>undertow</module>
<module>wildfly</module>
<module>wildfly-elytron</module>
</modules>
</project>

View file

@ -32,8 +32,21 @@
<modules>
<module>tomcat-core</module>
<module>tomcat6</module>
<module>tomcat7</module>
<module>tomcat8</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>tomcat6</module>
<module>tomcat7</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -29,8 +29,6 @@
<artifactId>keycloak-tomcat-core-adapter</artifactId>
<name>Keycloak Tomcat Core Integration</name>
<properties>
<!-- <tomcat.version>8.0.14</tomcat.version> -->
<!-- <tomcat.version>7.0.52</tomcat.version> -->
<tomcat.version>6.0.41</tomcat.version>
</properties>
<description />

View file

@ -0,0 +1,99 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.1.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
<name>Keycloak Wildfly Elytron OIDC Adapter</name>
<description/>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
<version>1.2.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.security.elytron-web</groupId>
<artifactId>undertow-server</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ElytronAccount implements OidcKeycloakAccount {
protected static Logger log = Logger.getLogger(ElytronAccount.class);
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
public ElytronAccount(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
this.principal = principal;
}
@Override
public RefreshableKeycloakSecurityContext getKeycloakSecurityContext() {
return principal.getKeycloakSecurityContext();
}
@Override
public Principal getPrincipal() {
return principal;
}
@Override
public Set<String> getRoles() {
Set<String> 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();
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext> 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<RefreshableKeycloakSecurityContext> 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);
}
}
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class ElytronHttpFacade implements OIDCHttpFacade {
private final HttpServerRequest request;
private final CallbackHandler callbackHandler;
private final AdapterTokenStore tokenStore;
private final AdapterDeploymentContext deploymentContext;
private Consumer<HttpServerResponse> 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<String> 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<HttpServerCookie> 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<String> 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<HttpServerResponse>() {
@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;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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<RefreshableKeycloakSecurityContext> principal) {
getElytronHttpFacade().authenticationComplete(new ElytronAccount(principal), true);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> 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;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(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());
}
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ElytronTokeStore extends AdapterTokenStore {
void logout(boolean glo);
}

View file

@ -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;
/**
* <p>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.
*
* <p>This listener should be automatically registered to a deployment using the subsystem.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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());
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
static final String NAME = "KEYCLOAK";
private final Map<String, ?> properties;
private final CallbackHandler callbackHandler;
private final AdapterDeploymentContext deploymentContext;
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> 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<String> sessions = httpFacade.getScopeIds(Scope.SESSION);
logoutHttpSessions(new ArrayList<>(sessions));
}
@Override
public void logoutHttpSessions(List<String> 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;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
private final AdapterDeploymentContext deploymentContext;
/**
* <p>Creates a new instance.
*
* <p>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<String, ?> properties) {
return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
}
@Override
public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
Map<String, Object> mechanismProperties = new HashMap();
mechanismProperties.putAll(properties);
if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext);
}
return null;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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 extends Credential> C getCredential(Class<C> 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<String> 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;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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;
}
}

View file

@ -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

View file

@ -32,7 +32,20 @@
<modules>
<module>wildfly-adapter</module>
<module>wf8-subsystem</module>
<module>wildfly-subsystem</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>wf8-subsystem</module>
</modules>
</profile>
</profiles>
</project>

0
adapters/oidc/wildfly/wildfly-adapter/pom.xml Executable file → Normal file
View file

View file

@ -30,6 +30,18 @@
<artifactId>keycloak-saml-eap-integration-pom</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>adapter</module>
<module>subsystem</module>

View file

@ -88,7 +88,6 @@
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>

View file

@ -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<String, String> attributes = new MultivaluedHashMap<>();
private MultivaluedHashMap<String, String> 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<String, List<String>> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
/**
* Convenience function that gets Attribute value by attribute friendly name
*

View file

@ -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());

View file

@ -35,9 +35,23 @@
<module>core</module>
<module>undertow</module>
<module>tomcat</module>
<module>jetty</module>
<module>wildfly</module>
<module>as7-eap6</module>
<module>servlet-filter</module>
<module>wildfly-elytron</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -32,8 +32,21 @@
<modules>
<module>tomcat-core</module>
<module>tomcat6</module>
<module>tomcat7</module>
<module>tomcat8</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>tomcat6</module>
<module>tomcat7</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,102 @@
<?xml version="1.0"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.1.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-saml-wildfly-elytron-adapter</artifactId>
<name>Keycloak WildFly Elytron SAML Adapter</name>
<description/>
<properties>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-api-public</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-adapter-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class ElytronHttpFacade implements HttpFacade {
private final HttpServerRequest request;
private final CallbackHandler callbackHandler;
private final SamlDeploymentContext deploymentContext;
private final SamlSessionStore sessionStore;
private Consumer<HttpServerResponse> 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<String> 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<HttpServerCookie> 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<String> 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<HttpServerResponse>() {
@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;
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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);
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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);
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String> sessions = idMapper.getUserSessions(principal);
if (sessions != null) {
List<String> ids = new LinkedList<>();
ids.addAll(sessions);
logoutSessionIds(ids);
for (String id : ids) {
idMapper.removeSession(id);
}
}
}
@Override
public void logoutBySsoId(List<String> ssoIds) {
if (ssoIds == null) return;
List<String> sessionIds = new LinkedList<>();
for (String id : ssoIds) {
String sessionId = idMapper.getSessionFromSSO(id);
if (sessionId != null) {
sessionIds.add(sessionId);
idMapper.removeSession(sessionId);
}
}
logoutSessionIds(sessionIds);
}
protected void logoutSessionIds(List<String> sessionIds) {
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();
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ElytronTokeStore {
void logout(boolean glo);
}

View file

@ -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;
/**
* <p>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.
*
* <p>This listener should be automatically registered to a deployment using the subsystem.
*
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
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());
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticationMechanism {
static Logger LOGGER = Logger.getLogger(KeycloakHttpServerAuthenticationMechanismFactory.class);
static final String NAME = "KEYCLOAK-SAML";
private final Map<String, ?> properties;
private final CallbackHandler callbackHandler;
private final SamlDeploymentContext deploymentContext;
private final SessionIdMapper idMapper;
public KeycloakHttpServerAuthenticationMechanism(Map<String, ?> 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);
}
}

View file

@ -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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakHttpServerAuthenticationMechanismFactory implements HttpServerAuthenticationMechanismFactory {
private SessionIdMapper idMapper = new InMemorySessionIdMapper();
private final SamlDeploymentContext deploymentContext;
/**
* <p>Creates a new instance.
*
* <p>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(SamlDeploymentContext deploymentContext) {
this.deploymentContext = deploymentContext;
}
@Override
public String[] getMechanismNames(Map<String, ?> properties) {
return new String[] {KeycloakHttpServerAuthenticationMechanism.NAME};
}
@Override
public HttpServerAuthenticationMechanism createAuthenticationMechanism(String mechanismName, Map<String, ?> properties, CallbackHandler callbackHandler) throws HttpAuthenticationException {
Map<String, Object> mechanismProperties = new HashMap();
mechanismProperties.putAll(properties);
if (KeycloakHttpServerAuthenticationMechanism.NAME.equals(mechanismName)) {
return new KeycloakHttpServerAuthenticationMechanism(properties, callbackHandler, this.deploymentContext, idMapper);
}
return null;
}
}

View file

@ -0,0 +1,109 @@
/*
* 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.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.adapters.saml.SamlPrincipal;
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.AuthorizationIdentity;
import org.wildfly.security.authz.MapAttributes;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.evidence.BearerTokenEvidence;
import org.wildfly.security.evidence.Evidence;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakSecurityRealm implements SecurityRealm {
@Override
public RealmIdentity getRealmIdentity(Principal principal) throws RealmUnavailableException {
if (principal instanceof SamlPrincipal) {
return createRealmIdentity((SamlPrincipal) principal);
}
return RealmIdentity.NON_EXISTENT;
}
private RealmIdentity createRealmIdentity(SamlPrincipal 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 extends Credential> C getCredential(Class<C> credentialType) throws RealmUnavailableException {
return null;
}
@Override
public SupportLevel getEvidenceVerifySupport(Class<? extends Evidence> evidenceType, String algorithmName) throws RealmUnavailableException {
if (isBearerTokenEvidence(evidenceType)) {
return SupportLevel.SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
@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 {
Map<String, List<String>> attributes = new HashMap<>(principal.getAttributes());
return AuthorizationIdentity.basicIdentity(new MapAttributes(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 {
if (isBearerTokenEvidence(evidenceType)) {
return SupportLevel.POSSIBLY_SUPPORTED;
}
return SupportLevel.UNSUPPORTED;
}
private boolean isBearerTokenEvidence(Class<?> evidenceType) {
return evidenceType != null && evidenceType.equals(BearerTokenEvidence.class);
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.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.adapters.saml.SamlPrincipal;
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 <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
final class SecurityIdentityUtil {
static final SecurityIdentity authorize(CallbackHandler callbackHandler, SamlPrincipal 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});
} catch (Exception e) {
throw new HttpAuthenticationException(e);
}
if (authorizeCallback.isAuthorized()) {
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;
}
}

View file

@ -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.saml.elytron.KeycloakHttpServerAuthenticationMechanismFactory

View file

@ -93,4 +93,18 @@
</plugins>
</build>
<profiles>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<build>
<finalName>${product.name}-${product.filename.version}-eap6-adapter</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -32,7 +32,20 @@
<modules>
<module>as7-modules</module>
<module>as7-adapter-zip</module>
<module>eap6-adapter-zip</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>as7-adapter-zip</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -85,4 +85,18 @@
</plugins>
</build>
<profiles>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<build>
<finalName>${product.name}-${product.filename.version}-js-adapter</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -33,17 +33,31 @@
<modules>
<module>as7-eap6-adapter</module>
<module>fuse-adapter-zip</module>
<module>js-adapter-zip</module>
<module>osgi</module>
<module>wildfly-adapter</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>jetty81-adapter-zip</module>
<module>jetty91-adapter-zip</module>
<module>jetty92-adapter-zip</module>
<module>jetty93-adapter-zip</module>
<module>jetty94-adapter-zip</module>
<module>js-adapter-zip</module>
<module>osgi</module>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>wf8-adapter</module>
<module>wildfly-adapter</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,57 @@
if (outcome != success) of /extension=org.keycloak.keycloak-adapter-subsystem:read-resource
/extension=org.keycloak.keycloak-adapter-subsystem/:add(module=org.keycloak.keycloak-adapter-subsystem)
else
echo Keycloak OpenID Connect Extension already installed
end-if
if (outcome != success) of /subsystem=keycloak:read-resource
/subsystem=keycloak:add
else
echo Keycloak OpenID Connect Subsystem already installed
end-if
if (outcome != success) of /subsystem=elytron/custom-realm=KeycloakOIDCRealm:read-resource
/subsystem=elytron/custom-realm=KeycloakOIDCRealm:add(class-name=org.keycloak.adapters.elytron.KeycloakSecurityRealm, module=org.keycloak.keycloak-wildfly-elytron-oidc-adapter)
else
echo Keycloak OpenID Connect Realm already installed
end-if
if (outcome != success) of /subsystem=elytron/security-domain=KeycloakDomain:read-resource
/subsystem=elytron/security-domain=KeycloakDomain:add(default-realm=KeycloakOIDCRealm,permission-mapper=default-permission-mapper,security-event-listener=local-audit,realms=[{realm=KeycloakOIDCRealm}])
else
echo Keycloak Security Domain already installed. Trying to install Keycloak OpenID Connect Realm.
/subsystem=elytron/security-domain=KeycloakDomain:list-add(name=realms, value={realm=KeycloakOIDCRealm})
end-if
if (outcome != success) of /subsystem=elytron/constant-realm-mapper=keycloak-oidc-realm-mapper:read-resource
/subsystem=elytron/constant-realm-mapper=keycloak-oidc-realm-mapper:add(realm-name=KeycloakOIDCRealm)
else
echo Keycloak OpenID Connect Realm Mapper already installed
end-if
if (outcome != success) of /subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-oidc-http-server-mechanism-factory:read-resource
/subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-oidc-http-server-mechanism-factory:add(module=org.keycloak.keycloak-wildfly-elytron-oidc-adapter)
else
echo Keycloak OpenID Connect HTTP Mechanism already installed
end-if
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-oidc-http-server-mechanism-factory, global])
else
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-oidc-http-server-mechanism-factory)
end-if
if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:add(security-domain=KeycloakDomain,http-server-mechanism-factory=keycloak-http-server-mechanism-factory,mechanism-configurations=[{mechanism-name=KEYCLOAK,mechanism-realm-configurations=[{realm-name=KeycloakOIDCRealm,realm-mapper=keycloak-oidc-realm-mapper}]}])
else
echo Keycloak HTTP Authentication Factory already installed. Trying to install Keycloak OpenID Connect Mechanism Configuration
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:list-add(name=mechanism-configurations, value={mechanism-name=KEYCLOAK,mechanism-realm-configurations=[{realm-name=KeycloakOIDCRealm,realm-mapper=keycloak-oidc-realm-mapper}]})
end-if
if (outcome != success) of /subsystem=undertow/application-security-domain=other:read-resource
/subsystem=undertow/application-security-domain=other:add(http-authentication-factory=keycloak-http-authentication)
else
echo Undertow already configured with Keycloak
end-if

View file

@ -35,6 +35,7 @@
<include>org/keycloak/keycloak-jboss-adapter-core/**</include>
<include>org/keycloak/keycloak-undertow-adapter/**</include>
<include>org/keycloak/keycloak-wildfly-adapter/**</include>
<include>org/keycloak/keycloak-wildfly-elytron-oidc-adapter/**</include>
<include>org/keycloak/keycloak-wildfly-subsystem/**</include>
<include>org/keycloak/keycloak-adapter-subsystem/**</include>
<include>org/keycloak/keycloak-servlet-oauth-client/**</include>
@ -57,5 +58,13 @@
<source>cli/adapter-install-offline.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
<file>
<source>../../shared-cli/adapter-elytron-install.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
<file>
<source>cli/adapter-elytron-install-offline.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
</assembly>

View file

@ -0,0 +1,59 @@
embed-server --server-config=standalone.xml
if (outcome != success) of /extension=org.keycloak.keycloak-adapter-subsystem:read-resource
/extension=org.keycloak.keycloak-adapter-subsystem/:add(module=org.keycloak.keycloak-adapter-subsystem)
else
echo Keycloak OpenID Connect Extension already installed
end-if
if (outcome != success) of /subsystem=keycloak:read-resource
/subsystem=keycloak:add
else
echo Keycloak OpenID Connect Subsystem already installed
end-if
if (outcome != success) of /subsystem=elytron/custom-realm=KeycloakOIDCRealm:read-resource
/subsystem=elytron/custom-realm=KeycloakOIDCRealm:add(class-name=org.keycloak.adapters.elytron.KeycloakSecurityRealm, module=org.keycloak.keycloak-wildfly-elytron-oidc-adapter)
else
echo Keycloak OpenID Connect Realm already installed
end-if
if (outcome != success) of /subsystem=elytron/security-domain=KeycloakDomain:read-resource
/subsystem=elytron/security-domain=KeycloakDomain:add(default-realm=KeycloakOIDCRealm,permission-mapper=default-permission-mapper,security-event-listener=local-audit,realms=[{realm=KeycloakOIDCRealm}])
else
echo Keycloak Security Domain already installed. Trying to install Keycloak OpenID Connect Realm.
/subsystem=elytron/security-domain=KeycloakDomain:list-add(name=realms, value={realm=KeycloakOIDCRealm})
end-if
if (outcome != success) of /subsystem=elytron/constant-realm-mapper=keycloak-oidc-realm-mapper:read-resource
/subsystem=elytron/constant-realm-mapper=keycloak-oidc-realm-mapper:add(realm-name=KeycloakOIDCRealm)
else
echo Keycloak OpenID Connect Realm Mapper already installed
end-if
if (outcome != success) of /subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-oidc-http-server-mechanism-factory:read-resource
/subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-oidc-http-server-mechanism-factory:add(module=org.keycloak.keycloak-wildfly-elytron-oidc-adapter)
else
echo Keycloak OpenID Connect HTTP Mechanism already installed
end-if
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-oidc-http-server-mechanism-factory, global])
else
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak OpenID Connect HTTP Mechanism Factory.
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-oidc-http-server-mechanism-factory)
end-if
if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:add(security-domain=KeycloakDomain,http-server-mechanism-factory=keycloak-http-server-mechanism-factory,mechanism-configurations=[{mechanism-name=KEYCLOAK,mechanism-realm-configurations=[{realm-name=KeycloakOIDCRealm,realm-mapper=keycloak-oidc-realm-mapper}]}])
else
echo Keycloak HTTP Authentication Factory already installed. Trying to install Keycloak OpenID Connect Mechanism Configuration
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:list-add(name=mechanism-configurations, value={mechanism-name=KEYCLOAK,mechanism-realm-configurations=[{realm-name=KeycloakOIDCRealm,realm-mapper=keycloak-oidc-realm-mapper}]})
end-if
if (outcome != success) of /subsystem=undertow/application-security-domain=other:read-resource
/subsystem=undertow/application-security-domain=other:add(http-authentication-factory=keycloak-http-authentication)
else
echo Undertow already configured with Keycloak
end-if

View file

@ -90,4 +90,18 @@
</plugins>
</build>
<profiles>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<build>
<finalName>${product.name}-${product.filename.version}-eap7-adapter</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -77,6 +77,10 @@
<module-def name="org.keycloak.keycloak-authz-client">
<maven-resource group="org.keycloak" artifact="keycloak-authz-client"/>
</module-def>
<module-def name="org.keycloak.keycloak-wildfly-elytron-oidc-adapter">
<maven-resource group="org.keycloak" artifact="keycloak-wildfly-elytron-oidc-adapter"/>
</module-def>
</target>
<target name="clean-target">

View file

@ -66,6 +66,10 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-subsystem</artifactId>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wildfly-elytron-oidc-adapter">
<properties>
<property name="jboss.api" value="private"/>
</properties>
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.bouncycastle" />
<module name="com.fasterxml.jackson.core.jackson-annotations"/>
<module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.fasterxml.jackson.core.jackson-databind"/>
<module name="com.fasterxml.jackson.jaxrs.jackson-jaxrs-json-provider"/>
<module name="org.apache.httpcomponents"/>
<module name="javax.servlet.api"/>
<module name="org.jboss.logging"/>
<module name="io.undertow.core"/>
<module name="io.undertow.servlet"/>
<module name="org.picketbox"/>
<module name="org.keycloak.keycloak-undertow-adapter"/>
<module name="org.keycloak.keycloak-adapter-spi"/>
<module name="org.keycloak.keycloak-adapter-core"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.wildfly.security.elytron"/>
</dependencies>
</module>

View file

@ -29,6 +29,14 @@
<name>Keycloak Demo Distribution</name>
<description/>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
@ -223,5 +231,7 @@
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -34,6 +34,14 @@
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<build>
<plugins>
<plugin>
@ -74,4 +82,6 @@
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -29,8 +29,14 @@
<name>Keycloak Examples Distribution</name>
<description/>
<dependencies>
</dependencies>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<build>
<finalName>keycloak-examples-${project.version}</finalName>
<plugins>
@ -101,5 +107,7 @@
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -24,39 +24,10 @@
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<!-- If war is filtered it will get corrupted. Only need to filter module.xml -->
<fileSets>
<fileSet>
<directory>target/${project.build.finalName}</directory>
<outputDirectory/>
<filtered>true</filtered>
<includes>
<include>**/module.xml</include>
<include>**/MANIFEST.MF</include>
</includes>
</fileSet>
<fileSet>
<directory>target/${project.build.finalName}</directory>
<outputDirectory/>
<filtered>false</filtered>
<excludes>
<exclude>**/module.xml</exclude>
<exclude>**/MANIFEST.MF</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>../../../</directory>
<includes>
<include>License.html</include>
</includes>
<outputDirectory>content</outputDirectory>
</fileSet>
<fileSet>
<directory>../../../themes/src/main/resources/theme</directory>
<outputDirectory>content/themes</outputDirectory>
<includes>
<include>**/**</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View file

@ -17,7 +17,7 @@
<build xmlns="urn:wildfly:feature-pack-build:1.1">
<dependencies>
<artifact name="org.wildfly:wildfly-feature-pack" />
<artifact name="${feature.parent}" />
</dependencies>
<config>
<standalone template="configuration/standalone/template.xml" subsystems="configuration/standalone/subsystems.xml" output-file="standalone/configuration/standalone.xml" />

View file

@ -47,27 +47,153 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-server-subsystem</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<type>zip</type>
<!-- Need to exlcude that in order to use the right guava version for drools -->
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions></executions>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>unpack-theme</id>
<phase>validate</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-themes</artifactId>
<outputDirectory>target/unpacked-themes</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-configuration</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/configuration</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/configuration</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-modules</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/modules</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/modules</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-content</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/content</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/content</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-identity</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/content/bin</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/identity</directory>
<includes>
<include>**/product.conf</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-identity-module</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/modules/system/layers/keycloak/org/jboss/as/product/${product.slot}</outputDirectory>
<resources>
<resource>
<directory>src/main/resources/identity/module</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-themes</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/content/themes</outputDirectory>
<resources>
<resource>
<directory>target/unpacked-themes/theme</directory>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-license</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/content</outputDirectory>
<resources>
<resource>
<directory>../../../</directory>
<includes>
<include>License.html</include>
</includes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.wildfly.build</groupId>
<artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
@ -80,6 +206,7 @@
<phase>compile</phase>
<configuration>
<config-file>feature-pack-build.xml</config-file>
<resources-dir>target/resources</resources-dir>
</configuration>
</execution>
</executions>
@ -111,4 +238,121 @@
</plugins>
</build>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<properties>
<feature.parent>org.wildfly:wildfly-feature-pack</feature.parent>
<xmlns.domain>urn:jboss:domain:4.0</xmlns.domain>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<type>zip</type>
</dependency>
</dependencies>
</profile>
<!-- Temporary profile to test with WildFly 11 -->
<profile>
<id>wf11</id>
<properties>
<xmlns.domain>urn:jboss:domain:5.0</xmlns.domain>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<version>${wildfly11.version}</version>
<type>zip</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-configuration-wf11</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/configuration</outputDirectory>
<resources>
<resource>
<directory>src/main/resources-wf11/configuration</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<properties>
<feature.parent>org.jboss.eap:wildfly-feature-pack</feature.parent>
<xmlns.domain>urn:jboss:domain:5.0</xmlns.domain>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.eap</groupId>
<artifactId>wildfly-feature-pack</artifactId>
<version>${eap.version}</version>
<type>zip</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-configuration-wf11</id>
<phase>validate</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>target/resources/configuration</outputDirectory>
<resources>
<resource>
<directory>src/main/resources-wf11/configuration</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,49 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--
~ 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.
-->
<!-- See src/resources/configuration/ReadMe.txt for how the configuration assembly works -->
<config>
<subsystems>
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="default">keycloak-datasources.xml</subsystem>
<subsystem>deployment-scanner.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem supplement="ha">ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem supplement="ha">keycloak-infinispan.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem supplement="default">jgroups.xml</subsystem>
<subsystem>jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem supplement="default">mod_cluster.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem supplement="standalone-wildfly">elytron.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem supplement="ha">undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
</config>

View file

@ -0,0 +1,47 @@
<?xml version='1.0' encoding='UTF-8'?>
<!--
~ 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.
-->
<!-- See src/resources/configuration/ReadMe.txt for how the configuration assembly works -->
<config>
<subsystems>
<subsystem>logging.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="default">keycloak-datasources2.xml</subsystem>
<subsystem>deployment-scanner.xml</subsystem>
<subsystem>ee.xml</subsystem>
<subsystem>ejb3.xml</subsystem>
<subsystem>io.xml</subsystem>
<subsystem>keycloak-infinispan2.xml</subsystem>
<subsystem>jaxrs.xml</subsystem>
<subsystem>jca.xml</subsystem>
<subsystem>jdr.xml</subsystem>
<subsystem>jmx.xml</subsystem>
<subsystem>jpa.xml</subsystem>
<subsystem>jsf.xml</subsystem>
<subsystem>mail.xml</subsystem>
<subsystem>naming.xml</subsystem>
<subsystem>remoting.xml</subsystem>
<subsystem>request-controller.xml</subsystem>
<subsystem>security-manager.xml</subsystem>
<subsystem supplement="standalone-wildfly">elytron.xml</subsystem>
<subsystem>security.xml</subsystem>
<subsystem>transactions.xml</subsystem>
<subsystem>undertow.xml</subsystem>
<subsystem>keycloak-server.xml</subsystem>
</subsystems>
</config>

View file

@ -0,0 +1,90 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>
</extensions>
<management>
<security-realms>
<security-realm name="ManagementRealm">
<authentication>
<local default-user="$local" skip-group-loading="true"/>
<properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization map-groups-to-roles="false">
<properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
<security-realm name="ApplicationRealm">
<server-identities>
<ssl>
<keystore path="application.keystore" relative-to="jboss.server.config.dir" keystore-password="password" alias="server" key-password="password" generate-self-signed-certificate-host="localhost"/>
</ssl>
</server-identities>
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization>
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
</security-realms>
<audit-log>
<formatters>
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
<handler name="file"/>
</handlers>
</logger>
</audit-log>
<management-interfaces>
<http-interface security-realm="ManagementRealm">
<http-upgrade enabled="true" />
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
<access-control provider="simple">
<role-mapping>
<role name="SuperUser">
<include>
<user name="$local"/>
</include>
</role>
</role-mapping>
</access-control>
</management>
<profile>
<?SUBSYSTEMS socket-binding-group="standard-sockets"?>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:127.0.0.1}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:127.0.0.1}"/>
</interface>
<?INTERFACES?>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<?SOCKET-BINDINGS?>
</socket-binding-group>
</server>

View file

@ -17,7 +17,7 @@
~ limitations under the License.
-->
<domain xmlns="urn:jboss:domain:4.0">
<domain xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>

View file

@ -22,7 +22,7 @@
is also started by this host controller file. The other instance must be started
via host-slave.xml
-->
<host name="master" xmlns="urn:jboss:domain:4.0">
<host name="master" xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>
</extensions>

View file

@ -17,7 +17,7 @@
~ limitations under the License.
-->
<host xmlns="urn:jboss:domain:4.0">
<host xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>
</extensions>

View file

@ -23,7 +23,7 @@
via host-slave.xml
-->
<host name="master" xmlns="urn:jboss:domain:4.0">
<host name="master" xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>
</extensions>

View file

@ -17,7 +17,7 @@
~ limitations under the License.
-->
<server xmlns="urn:jboss:domain:4.0">
<server xmlns="${xmlns.domain}">
<extensions>
<?EXTENSIONS?>

View file

@ -1,2 +1,2 @@
Any provider implementation jars and libraries in this folder will be loaded by Keycloak. See the providers
section in the documentation for more details.
Any provider implementation jars and libraries in this folder will be loaded. See the providers section in the
documentation for more details.

View file

@ -0,0 +1,3 @@
JBoss-Product-Release-Name: ${product.name.full}
JBoss-Product-Release-Version: ${product.version}
JBoss-Product-Console-Slot: ${product.wildfly.console.slot}

View file

@ -15,7 +15,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<module xmlns="urn:jboss:module:1.3" name="org.jboss.as.product" slot="keycloak">
<module xmlns="urn:jboss:module:1.3" name="org.jboss.as.product" slot="${product.slot}">
<properties>
<property name="jboss.api" value="private"/>
</properties>

View file

@ -0,0 +1 @@
slot=${product.slot}

View file

@ -1,3 +0,0 @@
JBoss-Product-Release-Name: ${product.name}
JBoss-Product-Release-Version: ${product.version}
JBoss-Product-Console-Slot: main

View file

@ -34,10 +34,6 @@
<modules>
<module>adapters</module>
<module>saml-adapters</module>
<module>demo-dist</module>
<module>api-docs-dist</module>
<module>examples-dist</module>
<module>proxy-dist</module>
<module>server-dist</module>
<module>server-overlay</module>
<module>feature-packs</module>
@ -47,7 +43,11 @@
<profile>
<id>jboss-release</id>
<modules>
<module>api-docs-dist</module>
<module>downloads</module>
<module>demo-dist</module>
<module>examples-dist</module>
<module>proxy-dist</module>
</modules>
</profile>
</profiles>

View file

@ -29,6 +29,14 @@
<name>Proxy Distro</name>
<description/>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
@ -69,5 +77,7 @@
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View file

@ -93,4 +93,18 @@
</plugins>
</build>
<profiles>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<build>
<finalName>${product.name}-${product.filename.version}-saml-eap6-adapter</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -32,7 +32,20 @@
<modules>
<module>as7-modules</module>
<module>as7-adapter-zip</module>
<module>eap6-adapter-zip</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>as7-adapter-zip</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -32,15 +32,29 @@
<modules>
<module>wildfly-adapter</module>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>as7-eap6-adapter</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty81-adapter-zip</module>
<!-- jetty 9.1 doesn't work right now
<module>jetty91-adapter-zip</module> -->
<module>jetty92-adapter-zip</module>
<module>jetty93-adapter-zip</module>
<module>jetty94-adapter-zip</module>
<module>as7-eap6-adapter</module>
<module>tomcat6-adapter-zip</module>
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
</modules>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,56 @@
if (outcome != success) of /extension=org.keycloak.keycloak-saml-adapter-subsystem:read-resource
/extension=org.keycloak.keycloak-saml-adapter-subsystem/:add(module=org.keycloak.keycloak-saml-adapter-subsystem)
else
echo Keycloak SAML Extension already installed
end-if
if (outcome != success) of /subsystem=keycloak-saml:read-resource
/subsystem=keycloak-saml:add
else
echo Keycloak SAML Subsystem already installed
end-if
if (outcome != success) of /subsystem=elytron/custom-realm=KeycloakSAMLRealm:read-resource
/subsystem=elytron/custom-realm=KeycloakSAMLRealm:add(class-name=org.keycloak.adapters.saml.elytron.KeycloakSecurityRealm, module=org.keycloak.keycloak-saml-wildfly-elytron-adapter)
else
echo Keycloak SAML Realm already installed
end-if
if (outcome != success) of /subsystem=elytron/security-domain=KeycloakDomain:read-resource
/subsystem=elytron/security-domain=KeycloakDomain:add(default-realm=KeycloakSAMLRealm,permission-mapper=default-permission-mapper,security-event-listener=local-audit,realms=[{realm=KeycloakSAMLRealm}])
else
echo Keycloak Security Domain already installed. Trying to install Keycloak SAML Realm.
/subsystem=elytron/security-domain=KeycloakDomain:list-add(name=realms, value={realm=KeycloakSAMLRealm})
end-if
if (outcome != success) of /subsystem=elytron/constant-realm-mapper=keycloak-saml-realm-mapper:read-resource
/subsystem=elytron/constant-realm-mapper=keycloak-saml-realm-mapper:add(realm-name=KeycloakSAMLRealm)
else
echo Keycloak SAML Realm Mapper already installed
end-if
if (outcome != success) of /subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-saml-http-server-mechanism-factory:read-resource
/subsystem=elytron/service-loader-http-server-mechanism-factory=keycloak-saml-http-server-mechanism-factory:add(module=org.keycloak.keycloak-saml-wildfly-elytron-adapter)
else
echo Keycloak SAML HTTP Mechanism Factory already installed
end-if
if (outcome != success) of /subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:read-resource
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:add(http-server-factories=[keycloak-saml-http-server-mechanism-factory, global])
else
echo Keycloak HTTP Mechanism Factory already installed. Trying to install Keycloak SAML HTTP Mechanism Factory.
/subsystem=elytron/aggregate-http-server-mechanism-factory=keycloak-http-server-mechanism-factory:list-add(name=http-server-factories, value=keycloak-saml-http-server-mechanism-factory)
end-if
if (outcome != success) of /subsystem=elytron/http-authentication-factory=keycloak-http-authentication:read-resource
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:add(security-domain=KeycloakDomain,http-server-mechanism-factory=keycloak-http-server-mechanism-factory,mechanism-configurations=[{mechanism-name=KEYCLOAK-SAML,mechanism-realm-configurations=[{realm-name=KeycloakSAMLCRealm,realm-mapper=keycloak-saml-realm-mapper}]}])
else
echo Keycloak HTTP Authentication Factory already installed. Trying to install Keycloak SAML Mechanism Configuration
/subsystem=elytron/http-authentication-factory=keycloak-http-authentication:list-add(name=mechanism-configurations, value={mechanism-name=KEYCLOAK-SAML,mechanism-realm-configurations=[{realm-name=KeycloakSAMLRealm,realm-mapper=keycloak-saml-realm-mapper}]})
end-if
if (outcome != success) of /subsystem=undertow/application-security-domain=other:read-resource
/subsystem=undertow/application-security-domain=other:add(http-authentication-factory=keycloak-http-authentication)
else
echo Undertow already configured with Keycloak
end-if

View file

@ -37,6 +37,7 @@
<include>org/keycloak/keycloak-jboss-adapter-core/**</include>
<include>org/keycloak/keycloak-saml-undertow-adapter/**</include>
<include>org/keycloak/keycloak-saml-wildfly-adapter/**</include>
<include>org/keycloak/keycloak-saml-wildfly-elytron-adapter/**</include>
<include>org/keycloak/keycloak-saml-wildfly-subsystem/**</include>
<include>org/keycloak/keycloak-saml-adapter-subsystem/**</include>
</includes>
@ -51,5 +52,9 @@
<source>../../shared-cli/adapter-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
<file>
<source>../../shared-cli/adapter-elytron-install-saml.cli</source>
<outputDirectory>bin</outputDirectory>
</file>
</files>
</assembly>

View file

@ -90,4 +90,18 @@
</plugins>
</build>
<profiles>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<build>
<finalName>${product.name}-${product.filename.version}-saml-eap7-adapter</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -76,6 +76,10 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-wildfly-subsystem"/>
</module-def>
<module-def name="org.keycloak.keycloak-saml-wildfly-elytron-adapter">
<maven-resource group="org.keycloak" artifact="keycloak-saml-wildfly-elytron-adapter"/>
</module-def>
</target>
<target name="clean-target">

View file

@ -70,6 +70,10 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wildfly-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wildfly-elytron-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wildfly-subsystem</artifactId>

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ * 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.
-->
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-saml-wildfly-elytron-adapter">
<properties>
<property name="jboss.api" value="private"/>
</properties>
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.bouncycastle" />
<module name="javax.servlet.api"/>
<module name="org.jboss.logging"/>
<module name="io.undertow.core"/>
<module name="io.undertow.servlet"/>
<module name="org.picketbox"/>
<module name="org.keycloak.keycloak-saml-undertow-adapter"/>
<module name="org.keycloak.keycloak-adapter-spi"/>
<module name="org.keycloak.keycloak-saml-core-public"/>
<module name="org.keycloak.keycloak-saml-core"/>
<module name="org.keycloak.keycloak-saml-adapter-api-public"/>
<module name="org.keycloak.keycloak-saml-adapter-core"/>
<module name="org.keycloak.keycloak-common"/>
<module name="org.apache.httpcomponents"/>
<module name="org.infinispan"/>
<module name="org.infinispan.commons"/>
<module name="org.infinispan.cachestore.remote"/>
<module name="org.infinispan.client.hotrod"/>
<module name="org.wildfly.security.elytron"/>
</dependencies>
</module>

View file

@ -39,21 +39,23 @@
<outputDirectory/>
<filtered>false</filtered>
<excludes>
<exclude>**/*.sh</exclude>
<exclude>**/module.xml</exclude>
<exclude>bin/*.sh</exclude>
<exclude>module.xml</exclude>
<exclude>welcome-content/**</exclude>
<exclude>appclient</exclude>
<exclude>appclient/**</exclude>
<exclude>bin/appclient.*</exclude>
<exclude>copyright.txt</exclude>
<exclude>README.txt</exclude>
<exclude>themes/**</exclude>
<exclude>version.txt</exclude>
<exclude>${profileExcludes}</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target/${project.build.finalName}</directory>
<outputDirectory/>
<includes>
<include>**/*.sh</include>
<include>bin/*.sh</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
@ -88,4 +90,13 @@
</includes>
</fileSet>
</fileSets>
<files>
<file>
<source>src/main/version.txt</source>
<outputDirectory/>
<filtered>true</filtered>
</file>
</files>
</assembly>

View file

@ -38,8 +38,6 @@
</dependencies>
<build>
<finalName>keycloak-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.wildfly.build</groupId>
@ -69,7 +67,7 @@
</goals>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
<descriptor>${assemblyFile}</descriptor>
</descriptors>
<recompressZippedFiles>true</recompressZippedFiles>
<finalName>${project.build.finalName}</finalName>
@ -107,4 +105,44 @@
</plugins>
</build>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<properties>
<assemblyFile>assembly.xml</assemblyFile>
</properties>
<build>
<finalName>keycloak-${project.version}</finalName>
</build>
</profile>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<properties>
<assemblyFile>assembly.xml</assemblyFile>
<profileExcludes>%regex[(providers.*)|(docs/contrib.*)|(docs/examples.*)|(docs/schema.*)]</profileExcludes>
</properties>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-dist</artifactId>
<type>zip</type>
</dependency>
</dependencies>
<build>
<finalName>${product.name}-${product.filename.version}</finalName>
</build>
</profile>
</profiles>
</project>

View file

@ -0,0 +1 @@
${product.name.full} - Version ${product.version}

View file

@ -18,11 +18,13 @@
<html>
<head>
<title>Welcome to Keycloak</title>
<meta http-equiv="refresh" content="0; url=/auth/" />
<meta name="robots" content="noindex, nofollow">
<script type="text/javascript">
window.location.href = "/auth/"
</script>
</head>
<body>
If you are not redirected automatically, follow this <a href='/auth'>link</a>.
</body>
</html>

View file

@ -26,34 +26,6 @@
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/modules/system/layers/keycloak</directory>
<outputDirectory>modules/system/add-ons/keycloak</outputDirectory>
<includes>
<include>**/**</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/themes</directory>
<outputDirectory>themes</outputDirectory>
<includes>
<include>**/**</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/providers</directory>
<outputDirectory>providers</outputDirectory>
<includes>
<include>**/**</include>
</includes>
</fileSet>
<fileSet>
<directory>../../</directory>
<includes>
<include>License.html</include>
</includes>
<outputDirectory></outputDirectory>
</fileSet>
<fileSet>
<directory>${project.build.directory}/cli</directory>
<includes>
@ -62,21 +34,21 @@
<outputDirectory>bin</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/bin</directory>
<directory>${project.build.directory}/unpacked/${serverDistDir}</directory>
<outputDirectory/>
<includes>
<include>add-user-keycloak.*</include>
<include>federation-sssd-setup.sh</include>
<include>kcadm.*</include>
<include>kcreg.*</include>
<include>**/**</include>
</includes>
<outputDirectory>bin</outputDirectory>
<excludes>
<exclude>modules/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/unpacked/keycloak-${project.version}/bin/client</directory>
<directory>${project.build.directory}/unpacked/${serverDistDir}/modules/system/layers/keycloak</directory>
<outputDirectory>modules/system/${identityType}/keycloak</outputDirectory>
<includes>
<include>keycloak*</include>
<include>**/**</include>
</includes>
<outputDirectory>bin/client</outputDirectory>
</fileSet>
</fileSets>

View file

@ -37,9 +37,22 @@
</dependency>
</dependencies>
<properties>
<commonFilesToInclude>
${serverDistDir}/modules/system/layers/keycloak/**,
${serverDistDir}/themes/**,
${serverDistDir}/providers/**,
${serverDistDir}/License.html,
${serverDistDir}/bin/client/keycloak*,
${serverDistDir}/bin/*keycloak*,
${serverDistDir}/bin/kc*,
${serverDistDir}/bin/federation-sssd-setup.sh,
${serverDistDir}/bin/migrate*
</commonFilesToInclude>
</properties>
<build>
<finalName>keycloak-overlay-${project.version}</finalName>
<resources></resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -77,6 +90,7 @@
<artifactId>keycloak-server-dist</artifactId>
<type>zip</type>
<outputDirectory>${project.build.directory}/unpacked</outputDirectory>
<includes>${filesToInclude}</includes>
</artifactItem>
</artifactItems>
</configuration>
@ -169,4 +183,38 @@
</plugins>
</build>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<properties>
<identityType>add-ons</identityType>
<serverDistDir>keycloak-${project.version}</serverDistDir>
<filesToInclude>${commonFilesToInclude}</filesToInclude>
</properties>
</profile>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<properties>
<identityType>layers</identityType>
<serverDistDir>${product.name}-${product.filename.version}</serverDistDir>
<filesToInclude>
${commonFilesToInclude},
${serverDistDir}/bin/product.conf,
${serverDistDir}/modules/layers.conf</filesToInclude>
</properties>
</profile>
</profiles>
</project>

View file

@ -50,6 +50,13 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.helper;
package org.keycloak.test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -27,20 +27,21 @@ import org.keycloak.admin.client.Keycloak;
import org.keycloak.client.registration.Auth;
import org.keycloak.client.registration.ClientRegistration;
import org.keycloak.client.registration.ClientRegistrationException;
import org.keycloak.representations.idm.ClientRepresentation;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.test.builders.ClientBuilder;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
public class TestsHelper {
@ -54,6 +55,8 @@ public class TestsHelper {
public static String appName;
public static int initialAccessTokenCount = 2;
protected static String clientConfiguration;
protected static String registrationAccessCode;
@ -80,30 +83,7 @@ public class TestsHelper {
}
public static String createDirectGrantClient() {
ClientRepresentation clientRepresentation = new ClientRepresentation();
clientRepresentation.setClientId("test-dga");
clientRepresentation.setFullScopeAllowed(true);
clientRepresentation.setPublicClient(Boolean.TRUE);
clientRepresentation.setDirectAccessGrantsEnabled(true);
ClientRegistration reg = ClientRegistration.create()
.url(keycloakBaseUrl, testRealm)
.build();
reg.auth(Auth.token(initialAccessCode));
try {
clientRepresentation = reg.create(clientRepresentation);
registrationAccessCode = clientRepresentation.getRegistrationAccessToken();
ObjectMapper mapper = new ObjectMapper();
reg.auth(Auth.token(registrationAccessCode));
clientConfiguration = mapper.writeValueAsString(reg.getAdapterConfig(clientRepresentation.getClientId()));
} catch (ClientRegistrationException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return clientConfiguration;
return createClient(ClientBuilder.create("test-dga").publicClient(true));
}
public static void deleteClient(String clientId) {
@ -169,7 +149,7 @@ public class TestsHelper {
}
public static boolean ImportTestRealm(String username, String password, String realmJsonPath) throws IOException {
public static boolean importTestRealm(String username, String password, String realmJsonPath) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ClassLoader classLoader = TestsHelper.class.getClassLoader();
@ -184,16 +164,12 @@ public class TestsHelper {
"admin-cli");
keycloak.realms().create(realmRepresentation);
testRealm = realmRepresentation.getRealm();
ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation();
rep.setCount(2);
rep.setExpiration(100);
ClientInitialAccessPresentation initialAccess = keycloak.realms().realm(testRealm).clientInitialAccess().create(rep);
initialAccessCode = initialAccess.getToken();
generateInitialAccessToken(keycloak);
return true;
}
public static boolean ImportTestRealm(String username, String password) throws IOException {
public static boolean importTestRealm(String username, String password) throws IOException {
testRealm = appName + "-realm";
RealmRepresentation realmRepresentation = new RealmRepresentation();
realmRepresentation.setRealm(testRealm);
@ -205,12 +181,16 @@ public class TestsHelper {
password,
"admin-cli");
keycloak.realms().create(realmRepresentation);
generateInitialAccessToken(keycloak);
return true;
}
private static void generateInitialAccessToken(Keycloak keycloak) {
ClientInitialAccessCreatePresentation rep = new ClientInitialAccessCreatePresentation();
rep.setCount(2);
rep.setCount(initialAccessTokenCount);
rep.setExpiration(100);
ClientInitialAccessPresentation initialAccess = keycloak.realms().realm(testRealm).clientInitialAccess().create(rep);
initialAccessCode = initialAccess.getToken();
return true;
}
public static boolean deleteRealm(String username, String password, String realmName) throws IOException {
@ -235,19 +215,20 @@ public class TestsHelper {
password,
"admin-cli");
UserRepresentation userRepresentation = new UserRepresentation();
userRepresentation.setUsername("testuser");
userRepresentation.setUsername(username);
userRepresentation.setEnabled(Boolean.TRUE);
Response response = keycloak.realms().realm(realmName).users().create(userRepresentation);
String userId = getCreatedId(response);
response.close();
CredentialRepresentation rep = new CredentialRepresentation();
rep.setType(CredentialRepresentation.PASSWORD);
rep.setValue("password");
rep.setValue(password);
rep.setTemporary(false);
keycloak.realms().realm(realmName).users().get(userId).resetPassword(rep);
//add roles
RoleRepresentation representation = new RoleRepresentation();
representation.setName("user");
keycloak.realms().realm(realmName).roles().create(representation);
RoleRepresentation realmRole = keycloak.realms().realm(realmName).roles().get("user").toRepresentation();
keycloak.realms().realm(realmName).users().get(userId).roles().realmLevel().add(Arrays.asList(realmRole));

View file

@ -0,0 +1,78 @@
/*
* 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.test.builders;
import org.keycloak.representations.idm.ClientRepresentation;
import java.util.Collections;
/**
* @author <a href="mailto:bruno@abstractj.org">Bruno Oliveira</a>
*/
public class ClientBuilder {
private ClientRepresentation rep;
public static ClientBuilder create(String clientId) {
ClientRepresentation rep = new ClientRepresentation();
rep.setEnabled(Boolean.TRUE);
rep.setClientId(clientId);
return new ClientBuilder(rep);
}
private ClientBuilder(ClientRepresentation rep) {
this.rep = rep;
}
public ClientRepresentation bearerOnly(boolean bearerOnly) {
rep.setBearerOnly(bearerOnly);
return rep;
}
public ClientBuilder rootUrl(String rootUrl) {
rep.setRootUrl(rootUrl);
return this;
}
public ClientBuilder redirectUri(String redirectUri) {
rep.setRedirectUris(Collections.singletonList(redirectUri));
return this;
}
public ClientBuilder baseUrl(String baseUrl) {
rep.setBaseUrl(baseUrl);
return this;
}
public ClientBuilder adminUrl(String adminUrl) {
rep.setAdminUrl(adminUrl);
return this;
}
public ClientRepresentation publicClient(boolean publicClient) {
rep.setFullScopeAllowed(true);
rep.setPublicClient(publicClient);
rep.setDirectAccessGrantsEnabled(true);
rep.setAdminUrl(rep.getRootUrl());
if (rep.getRedirectUris() == null && rep.getRootUrl() != null)
rep.setRedirectUris(Collections.singletonList(rep.getRootUrl().concat("/*")));
return rep;
}
}

85
pom.xml
View file

@ -1,3 +1,4 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
@ -36,18 +37,20 @@
<packaging>pom</packaging>
<properties>
<product.name>Keycloak</product.name>
<product.name-html>\u003Cdiv class="kc-logo-text"\u003E\u003Cspan\u003EKeycloak\u003C\u002Fspan\u003E\u003C\u002Fdiv\u003E</product.name-html>
<product.version>${project.version}</product.version>
<product.build-time>${timestamp}</product.build-time>
<product.default-profile>community</product.default-profile>
<!-- WildFly -->
<eap.version>7.0.0.Beta</eap.version>
<jboss.as.version>7.2.0.Final</jboss.as.version>
<wildfly.version>10.0.0.Final</wildfly.version>
<wildfly11.version>11.0.0.Alpha1</wildfly11.version> <!-- for testing with wf11 pre-releases -->
<eap.version>7.1.0.Alpha1-redhat-16</eap.version>
<wildfly.core.version>2.0.10.Final</wildfly.core.version>
<wildfly.build-tools.version>1.1.8.Final</wildfly.build-tools.version>
<aesh.version>0.66.12</aesh.version>
<version.org.wildfly.security.wildfly-elytron>1.1.0.Beta32</version.org.wildfly.security.wildfly-elytron>
<version.org.wildfly.security.elytron-web.undertow-server>1.0.0.Beta14</version.org.wildfly.security.elytron-web.undertow-server>
<aesh.version>0.66.15</aesh.version>
<apache.httpcomponents.version>4.5</apache.httpcomponents.version>
<apache.httpcomponents.httpcore.version>4.4.1</apache.httpcomponents.httpcore.version>
<apache.mime4j.version>0.6</apache.mime4j.version>
@ -75,8 +78,6 @@
<sun.jaxb.version>2.2.11</sun.jaxb.version>
<sun.xsom.version>20140925</sun.xsom.version>
<undertow.version>1.3.15.Final</undertow.version>
<wildfly.core.version>2.0.10.Final</wildfly.core.version>
<wildfly.build-tools.version>1.1.0.Final</wildfly.build-tools.version>
<xmlsec.version>2.0.5</xmlsec.version>
<!-- Authorization Drools Policy Provider -->
@ -112,6 +113,7 @@
<!-- Maven Plugins -->
<enforcer.plugin.version>1.4</enforcer.plugin.version>
<replacer.plugin.version>1.3.5</replacer.plugin.version>
<jar.plugin.version>3.0.2</jar.plugin.version>
<jboss.as.plugin.version>7.5.Final</jboss.as.plugin.version>
<jmeter.plugin.version>1.9.0</jmeter.plugin.version>
<jmeter.analysis.plugin.version>1.0.4</jmeter.analysis.plugin.version>
@ -178,7 +180,6 @@
<module>server-spi-private</module>
<module>saml-core-api</module>
<module>saml-core</module>
<module>proxy</module>
<module>federation</module>
<module>services</module>
<module>themes</module>
@ -360,6 +361,7 @@
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
@ -621,6 +623,16 @@
<artifactId>wildfly-web-common</artifactId>
<version>${wildfly.version}</version>
</dependency>
<dependency>
<groupId>org.wildfly.security</groupId>
<artifactId>wildfly-elytron</artifactId>
<version>${version.org.wildfly.security.wildfly-elytron}</version>
</dependency>
<dependency>
<groupId>org.wildfly.security.elytron-web</groupId>
<artifactId>undertow-server</artifactId>
<version>${version.org.wildfly.security.elytron-web.undertow-server}</version>
</dependency>
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
@ -921,6 +933,16 @@
<artifactId>keycloak-wildfly-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-elytron-oidc-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-wildfly-elytron-adapter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wildfly-adduser</artifactId>
@ -1386,6 +1408,11 @@
<argLine>${surefire.memory.settings}</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${jar.plugin.version}</version>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>maven-replacer-plugin</artifactId>
@ -1474,6 +1501,46 @@
</build>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<properties>
<product.name>Keycloak</product.name>
<product.name.full>Keycloak</product.name.full>
<product.slot>keycloak</product.slot>
<product.wildfly.console.slot>main</product.wildfly.console.slot>
<product.name-html>\u003Cdiv class="kc-logo-text"\u003E\u003Cspan\u003EKeycloak\u003C\u002Fspan\u003E\u003C\u002Fdiv\u003E</product.name-html>
<product.version>${project.version}</product.version>
<product.default-profile>community</product.default-profile>
</properties>
<modules>
<module>proxy</module>
</modules>
</profile>
<profile>
<id>product</id>
<activation>
<property>
<name>product</name>
</property>
</activation>
<properties>
<product.name.full>Red Hat Single Sign-On</product.name.full>
<product.name>rh-sso</product.name>
<product.slot>rh-sso</product.slot>
<product.wildfly.console.slot>eap</product.wildfly.console.slot>
<product.name-html>\u003Cstrong\u003ERed Hat\u003C\u002Fstrong\u003E\u003Csup\u003E\u00AE\u003C\u002Fsup\u003E Single Sign On</product.name-html>
<product.version>${project.version}</product.version>
<product.default-profile>product</product.default-profile>
<product.filename.version>7.1</product.filename.version>
</properties>
</profile>
<profile>
<id>distribution</id>
<modules>

View file

@ -33,6 +33,7 @@ import org.keycloak.migration.migrators.MigrateTo2_2_0;
import org.keycloak.migration.migrators.MigrateTo2_3_0;
import org.keycloak.migration.migrators.MigrateTo2_5_0;
import org.keycloak.migration.migrators.MigrateTo3_0_0;
import org.keycloak.migration.migrators.MigrateTo3_1_0;
import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession;
@ -58,7 +59,8 @@ public class MigrationModelManager {
new MigrateTo2_2_0(),
new MigrateTo2_3_0(),
new MigrateTo2_5_0(),
new MigrateTo3_0_0()
new MigrateTo3_0_0(),
new MigrateTo3_1_0()
};
public static void migrate(KeycloakSession session) {

View file

@ -0,0 +1,54 @@
/*
* 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.migration.migrators;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
*/
public class MigrateTo3_1_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("3.1.0");
@Override
public void migrate(KeycloakSession session) {
for (RealmModel realm : session.realms().getRealms()) {
if (realm.getBrowserSecurityHeaders() != null) {
Map<String, String> browserSecurityHeaders = new HashMap<>(realm.getBrowserSecurityHeaders());
browserSecurityHeaders.put("xRobotsTag", "none");
realm.setBrowserSecurityHeaders(Collections.unmodifiableMap(browserSecurityHeaders));
}
}
}
@Override
public ModelVersion getVersion() {
return VERSION;
}
}

View file

@ -34,11 +34,13 @@ public class BrowserSecurityHeaders {
headerMap.put("xFrameOptions", "X-Frame-Options");
headerMap.put("contentSecurityPolicy", "Content-Security-Policy");
headerMap.put("xContentTypeOptions", "X-Content-Type-Options");
headerMap.put("xRobotsTag", "X-Robots-Tag");
Map<String, String> dh = new HashMap<>();
dh.put("xFrameOptions", "SAMEORIGIN");
dh.put("contentSecurityPolicy", "frame-src 'self'");
dh.put("xContentTypeOptions", "nosniff");
dh.put("xRobotsTag", "none");
defaultHeaders = Collections.unmodifiableMap(dh);
headerAttributeMap = Collections.unmodifiableMap(headerMap);

Some files were not shown because too many files have changed in this diff Show more