Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Levente NAGY 2017-08-10 18:14:49 +02:00
commit c8aa708cff
908 changed files with 26364 additions and 53229 deletions

4
.gitignore vendored
View file

@ -49,3 +49,7 @@ target
# Maven shade # Maven shade
############# #############
*dependency-reduced-pom.xml *dependency-reduced-pom.xml
# nodejs #
##########
node_modules

View file

@ -1,33 +1,27 @@
language: java language: java
dist: precise
cache: cache:
directories: cache: false
- $HOME/.m2
before_cache:
- rm -rf $HOME/.m2/repository/org/keycloak
env: env:
global: global:
- MAVEN_SKIP_RC=true - MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m" - MAVEN_OPTS="-Xms512m -Xmx1536m"
matrix: matrix:
- TESTS=group1 - TESTS=unit
- TESTS=group2 - TESTS=server-group1
- TESTS=group3 - TESTS=server-group2
- TESTS=group4 - TESTS=server-group3
- TESTS=server-group4
- TESTS=old - TESTS=old
jdk: jdk:
- oraclejdk8 - oraclejdk8
before_script: install: true
- export MAVEN_SKIP_RC=true
install: script:
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q
script:
- ./travis-run-tests.sh $TESTS - ./travis-run-tests.sh $TESTS
sudo: false sudo: false

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -91,6 +91,8 @@ public class KeycloakDeployment {
// https://tools.ietf.org/html/rfc7636 // https://tools.ietf.org/html/rfc7636
protected boolean pkce = false; protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter; protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
public KeycloakDeployment() { public KeycloakDeployment() {
} }
@ -446,4 +448,14 @@ public class KeycloakDeployment {
public boolean isOAuthQueryParameterEnabled() { public boolean isOAuthQueryParameterEnabled() {
return !this.ignoreOAuthQueryParameter; return !this.ignoreOAuthQueryParameter;
} }
public Map<String, String> getRedirectRewriteRules() {
return redirectRewriteRules;
}
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
this.redirectRewriteRules = redirectRewriteRules;
}
} }

View file

@ -116,6 +116,7 @@ public class KeycloakDeploymentBuilder {
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests()); deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl()); deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter()); deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");

View file

@ -25,7 +25,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils; import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
@ -38,7 +37,10 @@ import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil; import org.keycloak.util.TokenUtil;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong; import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
/** /**
@ -141,6 +143,7 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) { protected String getRedirectUri(String state) {
String url = getRequestUrl(); String url = getRequestUrl();
log.debugf("callback uri: %s", url); log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort(); int port = sslRedirectPort();
if (port < 0) { if (port < 0) {
@ -170,7 +173,7 @@ public class OAuthRequestAuthenticator {
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters. .queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true"); .queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){ if(loginHint != null && loginHint.length() > 0){
@ -320,10 +323,11 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null; AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect(); strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try { try {
// For COOKIE store we don't have httpSessionId and single sign-out won't be available // For COOKIE store we don't have httpSessionId and single sign-out won't be available
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null; String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId); tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
} catch (ServerRequest.HttpFailure failure) { } catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token"); log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus()); log.error("status from server: " + failure.getStatus());
@ -375,6 +379,23 @@ public class OAuthRequestAuthenticator {
.replaceQueryParam(OAuth2Constants.STATE, null); .replaceQueryParam(OAuth2Constants.STATE, null);
return builder.build().toString(); return builder.build().toString();
} }
private String rewrittenRedirectUri(String originalUri) {
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
if(rewriteRules != null && !rewriteRules.isEmpty()) {
try {
URL url = new URL(originalUri);
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
redirectUriBuilder.append("://"+ url.getAuthority());
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
return redirectUriBuilder.toString();
} catch (MalformedURLException ex) {
log.error("Not a valid request url");
throw new RuntimeException(ex);
}
}
return originalUri;
}
} }

View file

@ -155,7 +155,9 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.refreshToken = response.getRefreshToken(); this.refreshToken = response.getRefreshToken();
} }
this.tokenString = tokenString; this.tokenString = tokenString;
tokenStore.refreshCallback(this); if (tokenStore != null) {
tokenStore.refreshCallback(this);
}
return true; return true;
} }

View file

@ -75,6 +75,7 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(10, deployment.getTokenMinimumTimeToLive()); assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests()); assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl()); assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
} }
@Test @Test

View file

@ -33,5 +33,8 @@
"token-minimum-time-to-live": 10, "token-minimum-time-to-live": 10,
"min-time-between-jwks-requests": 20, "min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120, "public-key-cache-ttl": 120,
"ignore-oauth-query-parameter": true "ignore-oauth-query-parameter": true,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
} }

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name> <name>Keycloak AS7 / JBoss EAP 6 Integration</name>

View file

@ -0,0 +1,9 @@
CLI Single Sign On
===================================
This java-based utility is meant for providing Keycloak integration to
command line applications that are either written in Java or another language. The
idea is that the Java app provided by this utility performs a login for a specific
client, parses responses, and exports an access token as an environment variable
that can be used by the command line utility you are accessing.

10
adapters/oidc/cli-sso/login.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
export KC_AUTH_SERVER=http://localhost:8080/auth
export KC_REALM=master
export KC_CLIENT=cli
export KC_ACCESS_TOKEN=`java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar login`

View file

@ -0,0 +1,9 @@
#!/bin/sh
java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar logout
unset KC_ACCESS_TOKEN

84
adapters/oidc/cli-sso/pom.xml Executable file
View file

@ -0,0 +1,84 @@
<?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.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-cli-sso</artifactId>
<name>Keycloak CLI SSO Framework</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-installed-adapter</artifactId>
</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.keycloak.adapters.KeycloakCliSsoMain</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,45 @@
/*
* 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;
import org.keycloak.adapters.installed.KeycloakCliSso;
import org.keycloak.adapters.installed.KeycloakInstalled;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.JsonSerialization;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakCliSsoMain extends KeycloakCliSso {
public static void main(String[] args) throws Exception {
new KeycloakCliSsoMain().mainCmd(args);
}
}

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -0,0 +1,266 @@
/*
* 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.installed;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.util.JsonSerialization;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakCliSso {
public void mainCmd(String[] args) throws Exception {
if (args.length != 1) {
printHelp();
return;
}
if (args[0].equalsIgnoreCase("login")) {
login();
} else if (args[0].equalsIgnoreCase("login-manual")) {
loginManual();
} else if (args[0].equalsIgnoreCase("token")) {
token();
} else if (args[0].equalsIgnoreCase("logout")) {
logout();
} else if (args[0].equalsIgnoreCase("env")) {
System.out.println(System.getenv().toString());
} else {
printHelp();
}
}
public void printHelp() {
System.err.println("Commands:");
System.err.println(" login - login with desktop browser if available, otherwise do manual login. Output is access token.");
System.err.println(" login-manual - manual login");
System.err.println(" token - print access token if logged in");
System.err.println(" logout - logout.");
System.exit(1);
}
public AdapterConfig getConfig() {
String url = System.getProperty("KEYCLOAK_AUTH_SERVER");
if (url == null) {
System.err.println("KEYCLOAK_AUTH_SERVER property not set");
System.exit(1);
}
String realm = System.getProperty("KEYCLOAK_REALM");
if (realm == null) {
System.err.println("KEYCLOAK_REALM property not set");
System.exit(1);
}
String client = System.getProperty("KEYCLOAK_CLIENT");
if (client == null) {
System.err.println("KEYCLOAK_CLIENT property not set");
System.exit(1);
}
String secret = System.getProperty("KEYCLOAK_CLIENT_SECRET");
AdapterConfig config = new AdapterConfig();
config.setAuthServerUrl(url);
config.setRealm(realm);
config.setResource(client);
config.setSslRequired("external");
if (secret != null) {
Map<String, Object> creds = new HashMap<>();
creds.put("secret", secret);
config.setCredentials(creds);
} else {
config.setPublicClient(true);
}
return config;
}
public boolean checkToken() throws Exception {
String token = getTokenResponse();
if (token == null) return false;
if (token != null) {
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
if (m.find()) {
String json = m.group(0);
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
return true;
}
AdapterConfig config = getConfig();
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
installed.refreshToken(tokenResponse.getRefreshToken());
processResponse(installed);
return true;
} catch (Exception e) {
System.err.println("Error processing existing token");
e.printStackTrace();
}
}
}
return false;
}
private String getTokenResponse() throws IOException {
String token = null;
File tokenFile = getTokenFilePath();
if (tokenFile.exists()) {
FileInputStream fis = new FileInputStream(tokenFile);
byte[] data = new byte[(int) tokenFile.length()];
fis.read(data);
fis.close();
token = new String(data, "UTF-8");
}
return token;
}
public void token() throws Exception {
String token = getTokenResponse();
if (token == null) {
System.err.println("There is no token for client");
System.exit(1);
} else {
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
if (m.find()) {
String json = m.group(0);
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
System.out.println(tokenResponse.getToken());
return;
} else {
System.err.println("token in response file is expired");
System.exit(1);
}
} catch (Exception e) {
System.err.println("Failure processing token response file");
e.printStackTrace();
System.exit(1);
}
} else {
System.err.println("Could not find json within token response file");
System.exit(1);
}
}
}
public void login() throws Exception {
if (checkToken()) return;
AdapterConfig config = getConfig();
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
installed.login();
processResponse(installed);
}
public String getHome() {
String home = System.getenv("HOME");
if (home == null) {
home = System.getProperty("HOME");
if (home == null) {
home = Paths.get("").toAbsolutePath().normalize().toString();
}
}
return home;
}
public File getTokenDirectory() {
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM")).toFile();
}
public File getTokenFilePath() {
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM"), System.getProperty("KEYCLOAK_CLIENT") + ".json").toFile();
}
private void processResponse(KeycloakInstalled installed) throws IOException {
AccessTokenResponse tokenResponse = installed.getTokenResponse();
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
tokenResponse.setIdToken(null);
String output = JsonSerialization.writeValueAsString(tokenResponse);
getTokenDirectory().mkdirs();
FileOutputStream fos = new FileOutputStream(getTokenFilePath());
fos.write(output.getBytes("UTF-8"));
fos.flush();
fos.close();
System.out.println(tokenResponse.getToken());
}
public void loginManual() throws Exception {
if (checkToken()) return;
AdapterConfig config = getConfig();
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
KeycloakInstalled installed = new KeycloakInstalled(deployment);
installed.loginManual();
processResponse(installed);
}
public void logout() throws Exception {
String token = getTokenResponse();
if (token != null) {
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
if (m.find()) {
String json = m.group(0);
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
if (Time.currentTime() > tokenResponse.getExpiresIn()) {
System.err.println("Login is expired");
System.exit(1);
}
AdapterConfig config = getConfig();
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
ServerRequest.invokeLogout(deployment, tokenResponse.getRefreshToken());
for (File fp : getTokenDirectory().listFiles()) fp.delete();
System.out.println("logout complete");
} catch (Exception e) {
System.err.println("Failure processing token response file");
e.printStackTrace();
System.exit(1);
}
} else {
System.err.println("Could not find json within token response file");
System.exit(1);
}
} else {
System.err.println("Not logged in");
System.exit(1);
}
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.adapters.installed; package org.keycloak.adapters.installed;
import org.apache.commons.codec.Charsets;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
@ -24,6 +25,7 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@ -43,6 +45,7 @@ import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -51,6 +54,11 @@ import java.util.concurrent.TimeUnit;
*/ */
public class KeycloakInstalled { public class KeycloakInstalled {
public interface HttpResponseWriter {
void success(PrintWriter pw, KeycloakInstalled ki);
void failure(PrintWriter pw, KeycloakInstalled ki);
}
private static final String KEYCLOAK_JSON = "META-INF/keycloak.json"; private static final String KEYCLOAK_JSON = "META-INF/keycloak.json";
private KeycloakDeployment deployment; private KeycloakDeployment deployment;
@ -59,12 +67,18 @@ public class KeycloakInstalled {
LOGGED_MANUAL, LOGGED_DESKTOP LOGGED_MANUAL, LOGGED_DESKTOP
} }
private AccessTokenResponse tokenResponse;
private String tokenString; private String tokenString;
private String idTokenString; private String idTokenString;
private IDToken idToken; private IDToken idToken;
private AccessToken token; private AccessToken token;
private String refreshToken; private String refreshToken;
private Status status; private Status status;
private Locale locale;
private HttpResponseWriter loginResponseWriter;
private HttpResponseWriter logoutResponseWriter;
public KeycloakInstalled() { public KeycloakInstalled() {
InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON); InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
@ -75,6 +89,92 @@ public class KeycloakInstalled {
deployment = KeycloakDeploymentBuilder.build(config); deployment = KeycloakDeploymentBuilder.build(config);
} }
public KeycloakInstalled(KeycloakDeployment deployment) {
this.deployment = deployment;
}
private static HttpResponseWriter defaultLoginWriter = new HttpResponseWriter() {
@Override
public void success(PrintWriter pw, KeycloakInstalled ki) {
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html");
pw.println();
pw.println("<html><h1>Login completed.</h1><div>");
pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
pw.println("</div></html>");
pw.flush();
}
@Override
public void failure(PrintWriter pw, KeycloakInstalled ki) {
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html");
pw.println();
pw.println("<html><h1>Login attempt failed.</h1><div>");
pw.println("</div></html>");
pw.flush();
}
};
private static HttpResponseWriter defaultLogoutWriter = new HttpResponseWriter() {
@Override
public void success(PrintWriter pw, KeycloakInstalled ki) {
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html");
pw.println();
pw.println("<html><h1>Logout completed.</h1><div>");
pw.println("You may close this browser tab.");
pw.println("</div></html>");
pw.flush();
}
@Override
public void failure(PrintWriter pw, KeycloakInstalled ki) {
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type: text/html");
pw.println();
pw.println("<html><h1>Logout failed.</h1><div>");
pw.println("You may close this browser tab.");
pw.println("</div></html>");
pw.flush();
}
};
public HttpResponseWriter getLoginResponseWriter() {
if (loginResponseWriter == null) {
return defaultLoginWriter;
} else {
return loginResponseWriter;
}
}
public HttpResponseWriter getLogoutResponseWriter() {
if (logoutResponseWriter == null) {
return defaultLogoutWriter;
} else {
return logoutResponseWriter;
}
}
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
this.loginResponseWriter = loginResponseWriter;
}
public void setLogoutResponseWriter(HttpResponseWriter logoutResponseWriter) {
this.logoutResponseWriter = logoutResponseWriter;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException { public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
if (isDesktopSupported()) { if (isDesktopSupported()) {
loginDesktop(); loginDesktop();
@ -108,19 +208,22 @@ public class KeycloakInstalled {
} }
public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException { public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException {
CallbackListener callback = new CallbackListener(); CallbackListener callback = new CallbackListener(getLoginResponseWriter());
callback.start(); callback.start();
String redirectUri = "http://localhost:" + callback.server.getLocalPort(); String redirectUri = "http://localhost:" + callback.server.getLocalPort();
String state = UUID.randomUUID().toString(); String state = UUID.randomUUID().toString();
String authUrl = deployment.getAuthUrl().clone() KeycloakUriBuilder builder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID) .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID);
.build().toString(); if (locale != null) {
builder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, locale.getLanguage());
}
String authUrl = builder.build().toString();
Desktop.getDesktop().browse(new URI(authUrl)); Desktop.getDesktop().browse(new URI(authUrl));
@ -144,7 +247,7 @@ public class KeycloakInstalled {
} }
private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException { private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException {
CallbackListener callback = new CallbackListener(); CallbackListener callback = new CallbackListener(getLogoutResponseWriter());
callback.start(); callback.start();
String redirectUri = "http://localhost:" + callback.server.getLocalPort(); String redirectUri = "http://localhost:" + callback.server.getLocalPort();
@ -167,9 +270,6 @@ public class KeycloakInstalled {
} }
public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException { public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException {
CallbackListener callback = new CallbackListener();
callback.start();
String redirectUri = "urn:ietf:wg:oauth:2.0:oob"; String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
String authUrl = deployment.getAuthUrl().clone() String authUrl = deployment.getAuthUrl().clone()
@ -208,7 +308,14 @@ public class KeycloakInstalled {
parseAccessToken(tokenResponse); parseAccessToken(tokenResponse);
} }
public void refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure, VerificationException {
AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
parseAccessToken(tokenResponse);
}
private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException { private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException {
this.tokenResponse = tokenResponse;
tokenString = tokenResponse.getToken(); tokenString = tokenResponse.getToken();
refreshToken = tokenResponse.getRefreshToken(); refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken(); idTokenString = tokenResponse.getIdToken();
@ -240,6 +347,10 @@ public class KeycloakInstalled {
return refreshToken; return refreshToken;
} }
public AccessTokenResponse getTokenResponse() {
return tokenResponse;
}
public boolean isDesktopSupported() { public boolean isDesktopSupported() {
return Desktop.isDesktopSupported(); return Desktop.isDesktopSupported();
} }
@ -248,6 +359,8 @@ public class KeycloakInstalled {
return deployment; return deployment;
} }
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException { private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null); AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
parseAccessToken(tokenResponse); parseAccessToken(tokenResponse);
@ -269,6 +382,7 @@ public class KeycloakInstalled {
return sb.toString(); return sb.toString();
} }
public class CallbackListener extends Thread { public class CallbackListener extends Thread {
private ServerSocket server; private ServerSocket server;
@ -283,14 +397,19 @@ public class KeycloakInstalled {
private String state; private String state;
public CallbackListener() throws IOException { private Socket socket;
private HttpResponseWriter writer;
public CallbackListener(HttpResponseWriter writer) throws IOException {
this.writer = writer;
server = new ServerSocket(0); server = new ServerSocket(0);
} }
@Override @Override
public void run() { public void run() {
try { try {
Socket socket = server.accept(); socket = server.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String request = br.readLine(); String request = br.readLine();
@ -314,10 +433,15 @@ public class KeycloakInstalled {
} }
} }
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
pw.println("Please close window and return to application"); PrintWriter pw = new PrintWriter(out);
pw.flush();
if (error == null) {
writer.success(pw, KeycloakInstalled.this);
} else {
writer.failure(pw, KeycloakInstalled.this);
}
pw.flush();
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
errorException = e; errorException = e;
@ -328,6 +452,8 @@ public class KeycloakInstalled {
} catch (IOException e) { } catch (IOException e) {
} }
} }
} }
} }

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Jetty Integration</name> <name>Keycloak Jetty Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -33,6 +33,13 @@
interval: 5 interval: 5
}; };
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) {
kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0];
}
}
kc.init = function (initOptions) { kc.init = function (initOptions) {
kc.authenticated = false; kc.authenticated = false;
@ -831,6 +838,10 @@
} }
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
if (kc.iframeVersion) {
src = src + '?version=' + kc.iframeVersion;
}
iframe.setAttribute('src', src ); iframe.setAttribute('src', src );
iframe.style.display = 'none'; iframe.style.display = 'none';
document.body.appendChild(iframe); document.body.appendChild(iframe);

View file

@ -28,7 +28,7 @@
} else if (!init) { } else if (!init) {
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
var url = location.href + "/init"; var url = location.href.split("?")[0] + "/init";
url += "?client_id=" + encodeURIComponent(clientId); url += "?client_id=" + encodeURIComponent(clientId);
url += "&origin=" + encodeURIComponent(origin); url += "&origin=" + encodeURIComponent(origin);

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak OIDC Client Adapter Modules</name> <name>Keycloak OIDC Client Adapter Modules</name>
@ -34,6 +34,7 @@
<module>adapter-core</module> <module>adapter-core</module>
<module>as7-eap6</module> <module>as7-eap6</module>
<module>installed</module> <module>installed</module>
<module>cli-sso</module>
<module>jaxrs-oauth-client</module> <module>jaxrs-oauth-client</module>
<module>jetty</module> <module>jetty</module>
<module>js</module> <module>js</module>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -54,72 +54,96 @@ import java.util.regex.Pattern;
*/ */
public class KeycloakOIDCFilter implements Filter { public class KeycloakOIDCFilter implements Filter {
private final static Logger log = Logger.getLogger("" + KeycloakOIDCFilter.class);
public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern"; public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
public static final String CONFIG_PATH_PARAM = "keycloak.config.path";
protected AdapterDeploymentContext deploymentContext; protected AdapterDeploymentContext deploymentContext;
protected SessionIdMapper idMapper = new InMemorySessionIdMapper(); protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
protected NodesRegistrationManagement nodesRegistrationManagement; protected NodesRegistrationManagement nodesRegistrationManagement;
protected Pattern skipPattern; protected Pattern skipPattern;
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class); private final KeycloakConfigResolver definedconfigResolver;
/**
* Constructor that can be used to define a {@code KeycloakConfigResolver} that will be used at initialization to
* provide the {@code KeycloakDeployment}.
* @param definedconfigResolver the resolver
*/
public KeycloakOIDCFilter(KeycloakConfigResolver definedconfigResolver) {
this.definedconfigResolver = definedconfigResolver;
}
public KeycloakOIDCFilter() {
this(null);
}
@Override @Override
public void init(final FilterConfig filterConfig) throws ServletException { public void init(final FilterConfig filterConfig) throws ServletException {
String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM); String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
if (skipPatternDefinition != null) { if (skipPatternDefinition != null) {
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL); skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
} }
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver"); if (definedconfigResolver != null) {
if (configResolverClass != null) { deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
try { log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
deploymentContext = new AdapterDeploymentContext(configResolver);
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
} else { } else {
String fp = filterConfig.getInitParameter("keycloak.config.file"); String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
InputStream is = null; if (configResolverClass != null) {
if (fp != null) {
try { try {
is = new FileInputStream(fp); KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
} catch (FileNotFoundException e) { deploymentContext = new AdapterDeploymentContext(configResolver);
throw new RuntimeException(e); log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
} }
} else { } else {
String path = "/WEB-INF/keycloak.json"; String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
String pathParam = filterConfig.getInitParameter("keycloak.config.path"); InputStream is = null;
if (pathParam != null) path = pathParam; if (fp != null) {
is = filterConfig.getServletContext().getResourceAsStream(path); try {
is = new FileInputStream(fp);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
} else {
String path = "/WEB-INF/keycloak.json";
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
if (pathParam != null) path = pathParam;
is = filterConfig.getServletContext().getResourceAsStream(path);
}
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration.");
} }
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.fine("Keycloak is using a per-deployment configuration.");
} }
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext); filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
nodesRegistrationManagement = new NodesRegistrationManagement(); nodesRegistrationManagement = new NodesRegistrationManagement();
} }
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) { private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
if (is == null) { if (is == null) {
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests."); log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
return new KeycloakDeployment(); return new KeycloakDeployment();
} }
return KeycloakDeploymentBuilder.build(is); return KeycloakDeploymentBuilder.build(is);
} }
@Override @Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
log.fine("Keycloak OIDC Filter"); log.fine("Keycloak OIDC Filter");
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
HttpServletRequest request = (HttpServletRequest) req; HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; HttpServletResponse response = (HttpServletResponse) res;
@ -201,7 +225,7 @@ public class KeycloakOIDCFilter implements Filter {
* *
* @param request the request to check * @param request the request to check
* @return {@code true} if the request should not be handled, * @return {@code true} if the request should not be handled,
* {@code false} otherwise. * {@code false} otherwise.
*/ */
private boolean shouldSkip(HttpServletRequest request) { private boolean shouldSkip(HttpServletRequest request) {

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -4,7 +4,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<artifactId>spring-boot-container-bundle</artifactId> <artifactId>spring-boot-container-bundle</artifactId>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -19,6 +19,9 @@ package org.keycloak.adapters.springsecurity.facade;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.KeycloakAccount;
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -57,7 +60,8 @@ public class SimpleHttpFacade implements OIDCHttpFacade {
SecurityContext context = SecurityContextHolder.getContext(); SecurityContext context = SecurityContextHolder.getContext();
if (context != null && context.getAuthentication() != null) { if (context != null && context.getAuthentication() != null) {
return (KeycloakSecurityContext) context.getAuthentication().getDetails(); KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) context.getAuthentication();
return authentication.getAccount().getKeycloakSecurityContext();
} }
return null; return null;

View file

@ -0,0 +1,41 @@
package org.keycloak.adapters.springsecurity.facade;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.spi.KeycloakAccount;
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.mockito.internal.util.collections.Sets;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.security.Principal;
import java.util.Set;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
public class SimpleHttpFacadeTest {
@Before
public void setup() {
SecurityContext springSecurityContext = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(springSecurityContext);
Set<String> roles = Sets.newSet("user");
Principal principal = mock(Principal.class);
RefreshableKeycloakSecurityContext keycloakSecurityContext = mock(RefreshableKeycloakSecurityContext.class);
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, keycloakSecurityContext);
KeycloakAuthenticationToken token = new KeycloakAuthenticationToken(account);
springSecurityContext.setAuthentication(token);
}
@Test
public void shouldRetrieveKeycloakSecurityContext() {
SimpleHttpFacade facade = new SimpleHttpFacade(new MockHttpServletRequest(), new MockHttpServletResponse());
assertNotNull(facade.getSecurityContext());
}
}

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Tomcat Integration</name> <name>Keycloak Tomcat Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-tomcat-integration-pom</artifactId> <artifactId>keycloak-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -22,7 +22,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -90,6 +90,11 @@ class ElytronHttpFacade implements OIDCHttpFacade {
void authenticationComplete() { void authenticationComplete() {
if (securityIdentity != null) { if (securityIdentity != null) {
HttpScope requestScope = request.getScope(Scope.EXCHANGE);
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
requestScope.setAttachment(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
this.request.authenticationComplete(response -> { this.request.authenticationComplete(response -> {
if (!restored) { if (!restored) {
responseConsumer.accept(response); responseConsumer.accept(response);

View file

@ -71,7 +71,7 @@ class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticat
AdapterDeploymentContext deploymentContext = getDeploymentContext(request); AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
if (deploymentContext == null) { if (deploymentContext == null) {
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI()); LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI(), getMechanismName());
request.noAuthenticationInProgress(); request.noAuthenticationInProgress();
return; return;
} }

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak WildFly Integration</name> <name>Keycloak WildFly Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>

View file

@ -37,6 +37,8 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD
public final class KeycloakAdapterConfigService { public final class KeycloakAdapterConfigService {
private static final String CREDENTIALS_JSON_NAME = "credentials"; private static final String CREDENTIALS_JSON_NAME = "credentials";
private static final String REDIRECT_REWRITE_RULE_JSON_NAME = "redirect-rewrite-rule";
private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService(); private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
@ -129,6 +131,56 @@ public final class KeycloakAdapterConfigService {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation)); ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(CREDENTIALS_JSON_NAME); return deployment.get(CREDENTIALS_JSON_NAME);
} }
public void addRedirectRewriteRule(ModelNode operation, ModelNode model) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
redirectRewritesRules = new ModelNode();
}
String redirectRewriteRuleName = redirectRewriteRule(operation);
if (!redirectRewriteRuleName.contains(".")) {
redirectRewritesRules.get(redirectRewriteRuleName).set(model.get("value").asString());
} else {
String[] parts = redirectRewriteRuleName.split("\\.");
String provider = parts[0];
String property = parts[1];
ModelNode redirectRewriteRule = redirectRewritesRules.get(provider);
if (!redirectRewriteRule.isDefined()) {
redirectRewriteRule = new ModelNode();
}
redirectRewriteRule.get(property).set(model.get("value").asString());
redirectRewritesRules.set(provider, redirectRewriteRule);
}
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME).set(redirectRewritesRules);
}
public void removeRedirectRewriteRule(ModelNode operation) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not remove redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.remove(ruleName);
}
public void updateRedirectRewriteRule(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode redirectRewritesRules = redirectRewriteRuleFromOp(operation);
if (!redirectRewritesRules.isDefined()) {
throw new RuntimeException("Can not update redirect rewrite rule. No rules defined for deployment in op " + operation.toString());
}
String ruleName = credentialNameFromOp(operation);
redirectRewritesRules.get(ruleName).set(resolvedValue);
}
private ModelNode redirectRewriteRuleFromOp(ModelNode operation) {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(REDIRECT_REWRITE_RULE_JSON_NAME);
}
private String realmNameFromOp(ModelNode operation) { private String realmNameFromOp(ModelNode operation) {
return valueFromOpAddress(RealmDefinition.TAG_NAME, operation); return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
@ -141,6 +193,10 @@ public final class KeycloakAdapterConfigService {
private String credentialNameFromOp(ModelNode operation) { private String credentialNameFromOp(ModelNode operation) {
return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation); return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
} }
private String redirectRewriteRule(ModelNode operation) {
return valueFromOpAddress(RedirecRewritetRuleDefinition.TAG_NAME, operation);
}
private String valueFromOpAddress(String addrElement, ModelNode operation) { private String valueFromOpAddress(String addrElement, ModelNode operation) {
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement); String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);

View file

@ -48,6 +48,7 @@ public class KeycloakExtension implements Extension {
static final RealmDefinition REALM_DEFINITION = new RealmDefinition(); static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition(); static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition(); static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
static final RedirecRewritetRuleDefinition REDIRECT_RULE_DEFINITON = new RedirecRewritetRuleDefinition();
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
@ -77,6 +78,7 @@ public class KeycloakExtension implements Extension {
registration.registerSubModel(REALM_DEFINITION); registration.registerSubModel(REALM_DEFINITION);
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION); ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
secureDeploymentRegistration.registerSubModel(REDIRECT_RULE_DEFINITON);
subsystem.registerXMLElementWriter(PARSER); subsystem.registerXMLElementWriter(PARSER);
} }

View file

@ -96,12 +96,17 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name)); PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>(); List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
List<ModelNode> redirectRulesToAdd = new ArrayList<ModelNode>();
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName(); String tagName = reader.getLocalName();
if (tagName.equals(CredentialDefinition.TAG_NAME)) { if (tagName.equals(CredentialDefinition.TAG_NAME)) {
readCredential(reader, addr, credentialsToAdd); readCredential(reader, addr, credentialsToAdd);
continue; continue;
} }
if (tagName.equals(RedirecRewritetRuleDefinition.TAG_NAME)) {
readRewriteRule(reader, addr, redirectRulesToAdd);
continue;
}
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName); SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName); if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
@ -111,6 +116,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
// Must add credentials after the deployment is added. // Must add credentials after the deployment is added.
resourcesToAdd.add(addSecureDeployment); resourcesToAdd.add(addSecureDeployment);
resourcesToAdd.addAll(credentialsToAdd); resourcesToAdd.addAll(credentialsToAdd);
resourcesToAdd.addAll(redirectRulesToAdd);
} }
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException { public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
@ -149,6 +155,43 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
} }
} }
} }
public void readRewriteRule(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> rewriteRuleToToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
Map<String, String> values = new HashMap<>();
String textValue = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == CHARACTERS) {
// text value of redirect rule element
String text = reader.getText();
if (text == null || text.trim().isEmpty()) {
continue;
}
textValue = text;
} else if (next == START_ELEMENT) {
String key = reader.getLocalName();
reader.next();
String value = reader.getText();
reader.next();
values.put(key, value);
} else if (next == END_ELEMENT) {
break;
}
}
if (textValue != null) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name, textValue);
rewriteRuleToToAdd.add(addRedirectRule);
} else {
for (Map.Entry<String, String> entry : values.entrySet()) {
ModelNode addRedirectRule = getRedirectRuleToAdd(parent, name + "." + entry.getKey(), entry.getValue());
rewriteRuleToToAdd.add(addRedirectRule);
}
}
}
private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) { private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode(); ModelNode addCredential = new ModelNode();
@ -158,6 +201,15 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
addCredential.get(CredentialDefinition.VALUE.getName()).set(value); addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
return addCredential; return addCredential;
} }
private ModelNode getRedirectRuleToAdd(PathAddress parent, String name, String value) {
ModelNode addRedirectRule = new ModelNode();
addRedirectRule.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(RedirecRewritetRuleDefinition.TAG_NAME, name));
addRedirectRule.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addRedirectRule.get(RedirecRewritetRuleDefinition.VALUE.getName()).set(value);
return addRedirectRule;
}
// expects that the current tag will have one single attribute called "name" // expects that the current tag will have one single attribute called "name"
private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException { private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
@ -219,6 +271,11 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
if (credentials.isDefined()) { if (credentials.isDefined()) {
writeCredentials(writer, credentials); writeCredentials(writer, credentials);
} }
ModelNode redirectRewriteRule = deploymentElements.get(RedirecRewritetRuleDefinition.TAG_NAME);
if (redirectRewriteRule.isDefined()) {
writeRedirectRules(writer, redirectRewriteRule);
}
writer.writeEndElement(); writer.writeEndElement();
} }
@ -265,6 +322,34 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
writer.writeEndElement(); writer.writeEndElement();
} }
} }
private void writeRedirectRules(XMLExtendedStreamWriter writer, ModelNode redirectRules) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property redirectRule : redirectRules.asPropertyList()) {
String ruleName = redirectRule.getName();
String ruleValue = redirectRule.getValue().get(RedirecRewritetRuleDefinition.VALUE.getName()).asString();
parsed.put(ruleName, ruleValue);
}
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(RedirecRewritetRuleDefinition.TAG_NAME);
writer.writeAttribute("name", entry.getKey());
Object value = entry.getValue();
if (value instanceof String) {
writeCharacters(writer, (String) value);
} else {
Map<String, String> redirectRulesProps = (Map<String, String>) value;
for (Map.Entry<String, String> prop : redirectRulesProps.entrySet()) {
writer.writeStartElement(prop.getKey());
writeCharacters(writer, prop.getValue());
writer.writeEndElement();
}
}
writer.writeEndElement();
}
}
// code taken from org.jboss.as.controller.AttributeMarshaller // code taken from org.jboss.as.controller.AttributeMarshaller
private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException { private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {

View file

@ -0,0 +1,61 @@
/*
* 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.subsystem.adapter.extension;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelType;
/**
*
* @author sblanc
*/
public class RedirecRewritetRuleDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "redirect-rewrite-rule";
protected static final AttributeDefinition VALUE =
new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
.build();
public RedirecRewritetRuleDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
new RedirectRewriteRuleAddHandler(VALUE),
RedirectRewriteRuleRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(VALUE, null, new RedirectRewriteRuleReadWriteAttributeHandler());
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleAddHandler extends AbstractAddStepHandler {
public RedirectRewriteRuleAddHandler(AttributeDefinition... attributes) {
super(attributes);
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addRedirectRewriteRule(operation, context.resolveExpressions(model));
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.updateRedirectRewriteRule(operation, attributeName, resolvedValue);
hh.setHandback(ckService);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateRedirectRewriteRule(operation, attributeName, valueToRestore);
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.subsystem.adapter.extension;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
public class RedirectRewriteRuleRemoveHandler extends AbstractRemoveStepHandler {
public static RedirectRewriteRuleRemoveHandler INSTANCE = new RedirectRewriteRuleRemoveHandler();
private RedirectRewriteRuleRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeRedirectRewriteRule(operation);
}
}

View file

@ -65,6 +65,7 @@ keycloak.secure-deployment.connection-pool-size=Connection pool size for the cli
keycloak.secure-deployment.resource=Application name keycloak.secure-deployment.resource=Application name
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
keycloak.secure-deployment.credentials=Adapter credentials keycloak.secure-deployment.credentials=Adapter credentials
keycloak.secure-deployment.redirect-rewrite-rule=Apply a rewrite rule for the redirect URI
keycloak.secure-deployment.bearer-only=Bearer Token Auth only keycloak.secure-deployment.bearer-only=Bearer Token Auth only
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
keycloak.secure-deployment.public-client=Public client keycloak.secure-deployment.public-client=Public client
@ -94,4 +95,9 @@ keycloak.secure-deployment.credential=Credential value
keycloak.credential=Credential keycloak.credential=Credential
keycloak.credential.value=Credential value keycloak.credential.value=Credential value
keycloak.credential.add=Credential add keycloak.credential.add=Credential add
keycloak.credential.remove=Credential remove keycloak.credential.remove=Credential remove
keycloak.redirect-rewrite-rule=redirect-rewrite-rule
keycloak.redirect-rewrite-rule.value=redirect-rewrite-rule value
keycloak.redirect-rewrite-rule.add=redirect-rewrite-rule add
keycloak.redirect-rewrite-rule.remove=redirect-rewrite-rule remove

View file

@ -101,6 +101,7 @@
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" /> <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/> <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/> <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="redirect-rewrite-rule" type="redirect-rewrite-rule-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
@ -127,4 +128,10 @@
</xs:sequence> </xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType> </xs:complexType>
<xs:complexType name="redirect-rewrite-rule-type" mixed="true">
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:any processContents="lax"></xs:any>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema> </xs:schema>

View file

@ -53,6 +53,7 @@
<auth-server-url>http://localhost:8080/auth</auth-server-url> <auth-server-url>http://localhost:8080/auth</auth-server-url>
<ssl-required>EXTERNAL</ssl-required> <ssl-required>EXTERNAL</ssl-required>
<credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential> <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">api/$1/</redirect-rewrite-rule>
</secure-deployment> </secure-deployment>
<secure-deployment name="http-endpoint"> <secure-deployment name="http-endpoint">
<realm>master</realm> <realm>master</realm>
@ -66,5 +67,6 @@
<credential name="jwt"> <credential name="jwt">
<client-keystore-file>/tmp/keystore.jks</client-keystore-file> <client-keystore-file>/tmp/keystore.jks</client-keystore-file>
</credential> </credential>
<redirect-rewrite-rule name="^/wsmaster/api/(.*)$">/api/$1/</redirect-rewrite-rule>
</secure-deployment> </secure-deployment>
</subsystem> </subsystem>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Adapters</name> <name>Keycloak Adapters</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-saml-eap-integration-pom</artifactId> <artifactId>keycloak-saml-eap-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak SAML EAP Integration</name> <name>Keycloak SAML EAP Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-eap-integration-pom</artifactId> <artifactId>keycloak-saml-eap-integration-pom</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View file

@ -81,7 +81,7 @@ public final class FormattingXMLStreamWriter implements XMLExtendedStreamWriter,
public void writeStartElement(final String localName) throws XMLStreamException { public void writeStartElement(final String localName) throws XMLStreamException {
ArrayDeque<String> namespaces = unspecifiedNamespaces; ArrayDeque<String> namespaces = unspecifiedNamespaces;
String namespace = namespaces.getFirst(); String namespace = namespaces.getFirst();
if (namespace != NO_NAMESPACE) { if (namespace == null ? NO_NAMESPACE != null : ! namespace.equals(NO_NAMESPACE)) {
writeStartElement(namespace, localName); writeStartElement(namespace, localName);
return; return;
} }
@ -140,9 +140,9 @@ public final class FormattingXMLStreamWriter implements XMLExtendedStreamWriter,
attrQueue.add(new ArgRunnable() { attrQueue.add(new ArgRunnable() {
public void run(int arg) throws XMLStreamException { public void run(int arg) throws XMLStreamException {
if (arg == 0) { if (arg == 0) {
delegate.writeStartElement(prefix, namespaceURI, localName); delegate.writeStartElement(prefix, localName, namespaceURI);
} else { } else {
delegate.writeEmptyElement(prefix, namespaceURI, localName); delegate.writeEmptyElement(prefix, localName, namespaceURI);
} }
} }
}); });
@ -165,14 +165,14 @@ public final class FormattingXMLStreamWriter implements XMLExtendedStreamWriter,
runAttrQueue(); runAttrQueue();
nl(); nl();
indent(); indent();
delegate.writeEmptyElement(prefix, namespaceURI, localName); delegate.writeEmptyElement(prefix, localName, namespaceURI);
state = END_ELEMENT; state = END_ELEMENT;
} }
@Override @Override
public void writeEmptyElement(final String localName) throws XMLStreamException { public void writeEmptyElement(final String localName) throws XMLStreamException {
String namespace = unspecifiedNamespaces.getFirst(); String namespace = unspecifiedNamespaces.getFirst();
if (namespace != NO_NAMESPACE) { if (namespace == null ? NO_NAMESPACE != null : ! namespace.equals(NO_NAMESPACE)) {
writeEmptyElement(namespace, localName); writeEmptyElement(namespace, localName);
return; return;
} }

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -18,7 +18,10 @@
package org.keycloak.adapters.saml; package org.keycloak.adapters.saml;
import org.keycloak.adapters.spi.AuthenticationError; import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType; import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import java.util.Objects;
/** /**
* Object that describes the SAML error that happened. * Object that describes the SAML error that happened.
@ -27,6 +30,7 @@ import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class SamlAuthenticationError implements AuthenticationError { public class SamlAuthenticationError implements AuthenticationError {
public static enum Reason { public static enum Reason {
EXTRACTION_FAILURE, EXTRACTION_FAILURE,
INVALID_SIGNATURE, INVALID_SIGNATURE,
@ -59,7 +63,18 @@ public class SamlAuthenticationError implements AuthenticationError {
@Override @Override
public String toString() { public String toString() {
return "SamlAuthenticationError [reason=" + reason + ", status=" + status + "]"; return "SamlAuthenticationError [reason=" + reason + ", status="
+ ((status == null || status.getStatus() == null) ? "UNKNOWN" : extractStatusCode(status.getStatus().getStatusCode()))
+ "]";
} }
private String extractStatusCode(StatusCodeType statusCode) {
if (statusCode == null || statusCode.getValue() == null) {
return "UNKNOWN";
}
if (Objects.equals(JBossSAMLURIConstants.STATUS_RESPONDER.get(), statusCode.getValue().toString())) {
return extractStatusCode(statusCode.getStatusCode());
}
return statusCode.getValue().toString();
}
} }

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -34,6 +34,7 @@ import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.saml.common.constants.JBossSAMLConstants; import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.core.util.NamespaceContext; import org.keycloak.saml.processing.core.util.NamespaceContext;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -65,9 +66,7 @@ public class SamlDescriptorIDPKeysExtractor {
MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>(); MultivaluedHashMap<String, KeyInfo> res = new MultivaluedHashMap<>();
try { try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = DocumentUtil.getDocumentBuilder();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(stream); Document doc = builder.parse(stream);
XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor"); XPathExpression expr = xpath.compile("/m:EntitiesDescriptor/m:EntityDescriptor/m:IDPSSODescriptor/m:KeyDescriptor");

View file

@ -407,8 +407,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
SubjectType subject = assertion.getSubject(); SubjectType subject = assertion.getSubject();
SubjectType.STSubType subType = subject.getSubType(); SubjectType.STSubType subType = subject.getSubType();
NameIDType subjectNameID = (NameIDType) subType.getBaseID(); NameIDType subjectNameID = subType == null ? null : (NameIDType) subType.getBaseID();
String principalName = subjectNameID.getValue(); String principalName = subjectNameID == null ? null : subjectNameID.getValue();
final Set<String> roles = new HashSet<>(); final Set<String> roles = new HashSet<>();
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
@ -473,7 +473,7 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
} }
URI nameFormat = subjectNameID.getFormat(); URI nameFormat = subjectNameID == null ? null : subjectNameID.getFormat();
String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString(); String nameFormatString = nameFormat == null ? JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get() : nameFormat.toString();
final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes); final SamlPrincipal principal = new SamlPrincipal(assertion, principalName, principalName, nameFormatString, attributes, friendlyAttributes);
String index = authn == null ? null : authn.getSessionIndex(); String index = authn == null ? null : authn.getSessionIndex();

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak SAML Jetty Integration</name> <name>Keycloak SAML Jetty Integration</name>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak SAML Client Adapter Modules</name> <name>Keycloak SAML Client Adapter Modules</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak SAML Tomcat Integration</name> <name>Keycloak SAML Tomcat Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId> <artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId> <artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId> <artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-saml-tomcat-integration-pom</artifactId> <artifactId>keycloak-saml-tomcat-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -41,10 +41,20 @@ import java.util.List;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve { public class SamlAuthenticatorValve extends AbstractSamlAuthenticatorValve {
/**
* Method called by Tomcat &lt; 8.5.5
*/
public boolean authenticate(Request request, HttpServletResponse response) throws IOException { public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
return authenticateInternal(request, response, request.getContext().getLoginConfig()); return authenticateInternal(request, response, request.getContext().getLoginConfig());
} }
/**
* Method called by Tomcat &gt;= 8.5.5
*/
protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException {
return this.authenticate(request, response);
}
@Override @Override
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException { protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
if (loginConfig == null) return false; if (loginConfig == null) return false;

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -47,10 +47,8 @@ import org.wildfly.security.auth.callback.AnonymousAuthorizationCallback;
import org.wildfly.security.auth.callback.AuthenticationCompleteCallback; import org.wildfly.security.auth.callback.AuthenticationCompleteCallback;
import org.wildfly.security.auth.callback.SecurityIdentityCallback; import org.wildfly.security.auth.callback.SecurityIdentityCallback;
import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpScope; import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpServerCookie; import org.wildfly.security.http.HttpServerCookie;
import org.wildfly.security.http.HttpServerMechanismsResponder;
import org.wildfly.security.http.HttpServerRequest; import org.wildfly.security.http.HttpServerRequest;
import org.wildfly.security.http.HttpServerResponse; import org.wildfly.security.http.HttpServerResponse;
import org.wildfly.security.http.Scope; import org.wildfly.security.http.Scope;
@ -87,11 +85,14 @@ class ElytronHttpFacade implements HttpFacade {
void authenticationComplete() { void authenticationComplete() {
this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, samlSession.getPrincipal()); this.securityIdentity = SecurityIdentityUtil.authorize(this.callbackHandler, samlSession.getPrincipal());
this.request.authenticationComplete(response -> {
if (!restored) { if (this.securityIdentity != null) {
responseConsumer.accept(response); this.request.authenticationComplete(response -> {
} if (!restored) {
}, () -> ((ElytronTokeStore) sessionStore).logout(true)); responseConsumer.accept(response);
}
}, () -> ((ElytronTokeStore) sessionStore).logout(true));
}
} }
void authenticationCompleteAnonymous() { void authenticationCompleteAnonymous() {

View file

@ -65,7 +65,7 @@ class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticat
SamlDeploymentContext deploymentContext = getDeploymentContext(request); SamlDeploymentContext deploymentContext = getDeploymentContext(request);
if (deploymentContext == null) { if (deploymentContext == null) {
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI()); LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI(), getMechanismName());
request.noAuthenticationInProgress(); request.noAuthenticationInProgress();
return; return;
} }

View file

@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak SAML Wildfly Integration</name> <name>Keycloak SAML Wildfly Integration</name>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View file

@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.CR1-SNAPSHOT</version> <version>3.3.0.CR1-SNAPSHOT</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

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