Merge pull request #138 from patriot1burke/master
refactor out resteasy from adapters
This commit is contained in:
commit
addefbb587
57 changed files with 2444 additions and 670 deletions
|
@ -1,155 +0,0 @@
|
||||||
package org.keycloak;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
|
||||||
import org.jboss.resteasy.util.BasicAuthHelper;
|
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.core.Form;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class AbstractOAuthClient {
|
|
||||||
public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
|
|
||||||
protected String clientId;
|
|
||||||
protected String password;
|
|
||||||
protected KeyStore truststore;
|
|
||||||
protected String authUrl;
|
|
||||||
protected String codeUrl;
|
|
||||||
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
|
||||||
protected String stateCookiePath;
|
|
||||||
protected Client client;
|
|
||||||
protected boolean isSecure;
|
|
||||||
protected final AtomicLong counter = new AtomicLong();
|
|
||||||
|
|
||||||
protected String getStateCode() {
|
|
||||||
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Client for obtaining access token from code
|
|
||||||
*/
|
|
||||||
public void start() {
|
|
||||||
if (client == null) {
|
|
||||||
client = new ResteasyClientBuilder().trustStore(truststore)
|
|
||||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
|
||||||
.connectionPoolSize(10)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* closes cllient
|
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientId() {
|
|
||||||
return clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientId(String clientId) {
|
|
||||||
this.clientId = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public KeyStore getTruststore() {
|
|
||||||
return truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTruststore(KeyStore truststore) {
|
|
||||||
this.truststore = truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAuthUrl() {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setAuthUrl(String authUrl) {
|
|
||||||
this.authUrl = authUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getCodeUrl() {
|
|
||||||
return codeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCodeUrl(String codeUrl) {
|
|
||||||
this.codeUrl = codeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStateCookieName() {
|
|
||||||
return stateCookieName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStateCookieName(String stateCookieName) {
|
|
||||||
this.stateCookieName = stateCookieName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getStateCookiePath() {
|
|
||||||
return stateCookiePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStateCookiePath(String stateCookiePath) {
|
|
||||||
this.stateCookiePath = stateCookiePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Client getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClient(Client client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String resolveBearerToken(String redirectUri, String code) {
|
|
||||||
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
|
||||||
String authHeader = BasicAuthHelper.createHeader(clientId, password);
|
|
||||||
Form codeForm = new Form()
|
|
||||||
.param("grant_type", "authorization_code")
|
|
||||||
.param("code", code)
|
|
||||||
.param("client_id", clientId)
|
|
||||||
.param("password", password)
|
|
||||||
.param("redirect_uri", redirectUri);
|
|
||||||
Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
|
|
||||||
try {
|
|
||||||
if (res.getStatus() == 400) {
|
|
||||||
throw new BadRequestException();
|
|
||||||
} else if (res.getStatus() != 200) {
|
|
||||||
throw new InternalServerErrorException(new Exception("Unknown error when getting acess token"));
|
|
||||||
}
|
|
||||||
AccessTokenResponse tokenResponse = res.readEntity(AccessTokenResponse.class);
|
|
||||||
return tokenResponse.getToken();
|
|
||||||
} finally {
|
|
||||||
res.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String stripOauthParametersFromRedirect(String uri) {
|
|
||||||
UriBuilder builder = UriBuilder.fromUri(uri)
|
|
||||||
.replaceQueryParam("code", null)
|
|
||||||
.replaceQueryParam("state", null);
|
|
||||||
return builder.build().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
94
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file
94
core/src/main/java/org/keycloak/AbstractOAuthClient.java
Executable file
|
@ -0,0 +1,94 @@
|
||||||
|
package org.keycloak;
|
||||||
|
|
||||||
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class AbstractOAuthClient {
|
||||||
|
public static final String OAUTH_TOKEN_REQUEST_STATE = "OAuth_Token_Request_State";
|
||||||
|
protected String clientId;
|
||||||
|
protected String password;
|
||||||
|
protected KeyStore truststore;
|
||||||
|
protected String authUrl;
|
||||||
|
protected String codeUrl;
|
||||||
|
protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE;
|
||||||
|
protected String stateCookiePath;
|
||||||
|
protected boolean isSecure;
|
||||||
|
protected final AtomicLong counter = new AtomicLong();
|
||||||
|
|
||||||
|
protected String getStateCode() {
|
||||||
|
return counter.getAndIncrement() + "/" + UUID.randomUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getClientId() {
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClientId(String clientId) {
|
||||||
|
this.clientId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyStore getTruststore() {
|
||||||
|
return truststore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTruststore(KeyStore truststore) {
|
||||||
|
this.truststore = truststore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthUrl() {
|
||||||
|
return authUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void setAuthUrl(String authUrl) {
|
||||||
|
this.authUrl = authUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCodeUrl() {
|
||||||
|
return codeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeUrl(String codeUrl) {
|
||||||
|
this.codeUrl = codeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStateCookieName() {
|
||||||
|
return stateCookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateCookieName(String stateCookieName) {
|
||||||
|
this.stateCookieName = stateCookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStateCookiePath() {
|
||||||
|
return stateCookiePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateCookiePath(String stateCookiePath) {
|
||||||
|
this.stateCookiePath = stateCookiePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String stripOauthParametersFromRedirect(String uri) {
|
||||||
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
|
||||||
|
.replaceQueryParam("code", null)
|
||||||
|
.replaceQueryParam("state", null);
|
||||||
|
return builder.build().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,4 +34,18 @@ public class SkeletonKeySession implements Serializable {
|
||||||
return metadata;
|
return metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static ThreadLocal<SkeletonKeySession> local = new ThreadLocal<SkeletonKeySession>();
|
||||||
|
|
||||||
|
public static void pushContext(SkeletonKeySession session) {
|
||||||
|
local.set(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearContext() {
|
||||||
|
local.set(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SkeletonKeySession getContext() {
|
||||||
|
return local.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
526
core/src/main/java/org/keycloak/util/Encode.java
Executable file
526
core/src/main/java/org/keycloak/util/Encode.java
Executable file
|
@ -0,0 +1,526 @@
|
||||||
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.CharacterCodingException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.CharsetDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
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 Encode
|
||||||
|
{
|
||||||
|
private static final String UTF_8 = "UTF-8";
|
||||||
|
|
||||||
|
private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");
|
||||||
|
|
||||||
|
private static final String[] pathEncoding = new String[128];
|
||||||
|
private static final String[] pathSegmentEncoding = new String[128];
|
||||||
|
private static final String[] matrixParameterEncoding = new String[128];
|
||||||
|
private static final String[] queryNameValueEncoding = new String[128];
|
||||||
|
private static final String[] queryStringEncoding = new String[128];
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||||
|
*
|
||||||
|
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
|
/ "*" / "+" / "," / ";" / "="
|
||||||
|
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < 128; i++)
|
||||||
|
{
|
||||||
|
if (i >= 'a' && i <= 'z') continue;
|
||||||
|
if (i >= 'A' && i <= 'Z') continue;
|
||||||
|
if (i >= '0' && i <= '9') continue;
|
||||||
|
switch ((char) i)
|
||||||
|
{
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '_':
|
||||||
|
case '~':
|
||||||
|
case '!':
|
||||||
|
case '$':
|
||||||
|
case '&':
|
||||||
|
case '\'':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
case ',':
|
||||||
|
case '/':
|
||||||
|
case ';':
|
||||||
|
case '=':
|
||||||
|
case ':':
|
||||||
|
case '@':
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append((char) i);
|
||||||
|
pathEncoding[i] = URLEncoder.encode(sb.toString());
|
||||||
|
}
|
||||||
|
pathEncoding[' '] = "%20";
|
||||||
|
System.arraycopy(pathEncoding, 0, matrixParameterEncoding, 0, pathEncoding.length);
|
||||||
|
matrixParameterEncoding[';'] = "%3B";
|
||||||
|
matrixParameterEncoding['='] = "%3D";
|
||||||
|
matrixParameterEncoding['/'] = "%2F"; // RESTEASY-729
|
||||||
|
System.arraycopy(pathEncoding, 0, pathSegmentEncoding, 0, pathEncoding.length);
|
||||||
|
pathSegmentEncoding['/'] = "%2F";
|
||||||
|
/*
|
||||||
|
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
||||||
|
*
|
||||||
|
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
* space encoded as '+'
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < 128; i++)
|
||||||
|
{
|
||||||
|
if (i >= 'a' && i <= 'z') continue;
|
||||||
|
if (i >= 'A' && i <= 'Z') continue;
|
||||||
|
if (i >= '0' && i <= '9') continue;
|
||||||
|
switch ((char) i)
|
||||||
|
{
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '_':
|
||||||
|
case '~':
|
||||||
|
case '?':
|
||||||
|
continue;
|
||||||
|
case ' ':
|
||||||
|
queryNameValueEncoding[i] = "+";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append((char) i);
|
||||||
|
queryNameValueEncoding[i] = URLEncoder.encode(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* query = *( pchar / "/" / "?" )
|
||||||
|
|
||||||
|
*/
|
||||||
|
for (int i = 0; i < 128; i++)
|
||||||
|
{
|
||||||
|
if (i >= 'a' && i <= 'z') continue;
|
||||||
|
if (i >= 'A' && i <= 'Z') continue;
|
||||||
|
if (i >= '0' && i <= '9') continue;
|
||||||
|
switch ((char) i)
|
||||||
|
{
|
||||||
|
case '-':
|
||||||
|
case '.':
|
||||||
|
case '_':
|
||||||
|
case '~':
|
||||||
|
case '!':
|
||||||
|
case '$':
|
||||||
|
case '&':
|
||||||
|
case '\'':
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '*':
|
||||||
|
case '+':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
case '=':
|
||||||
|
case ':':
|
||||||
|
case '@':
|
||||||
|
case '?':
|
||||||
|
case '/':
|
||||||
|
continue;
|
||||||
|
case ' ':
|
||||||
|
queryStringEncoding[i] = "%20";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
StringBuffer sb = new StringBuffer();
|
||||||
|
sb.append((char) i);
|
||||||
|
queryStringEncoding[i] = URLEncoder.encode(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%..." and template parameters intact.
|
||||||
|
*/
|
||||||
|
public static String encodeQueryString(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, queryStringEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%...", matrix parameters, template parameters, and '/' characters intact.
|
||||||
|
*/
|
||||||
|
public static String encodePath(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, pathEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%...", matrix parameters and template parameters intact.
|
||||||
|
*/
|
||||||
|
public static String encodePathSegment(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, pathSegmentEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%..." and template parameters intact.
|
||||||
|
*/
|
||||||
|
public static String encodeFragment(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, queryNameValueEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%..." and template parameters intact.
|
||||||
|
*/
|
||||||
|
public static String encodeMatrixParam(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, matrixParameterEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%..." and template parameters intact.
|
||||||
|
*/
|
||||||
|
public static String encodeQueryParam(String value)
|
||||||
|
{
|
||||||
|
return encodeValue(value, queryNameValueEncoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
//private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|$)");
|
||||||
|
private static final Pattern nonCodes = Pattern.compile("%([^a-fA-F0-9]|[a-fA-F0-9]$|$|[a-fA-F0-9][^a-fA-F0-9])");
|
||||||
|
private static final Pattern encodedChars = Pattern.compile("%([a-fA-F0-9][a-fA-F0-9])");
|
||||||
|
private static final Pattern encodedCharsMulti = Pattern.compile("((%[a-fA-F0-9][a-fA-F0-9])+)");
|
||||||
|
|
||||||
|
public static String decodePath(String path)
|
||||||
|
{
|
||||||
|
Matcher matcher = encodedCharsMulti.matcher(path);
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();
|
||||||
|
while (matcher.find())
|
||||||
|
{
|
||||||
|
decoder.reset();
|
||||||
|
String decoded = decodeBytes(matcher.group(1), decoder);
|
||||||
|
decoded = decoded.replace("\\", "\\\\");
|
||||||
|
decoded = decoded.replace("$", "\\$");
|
||||||
|
matcher.appendReplacement(buf, decoded);
|
||||||
|
}
|
||||||
|
matcher.appendTail(buf);
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String decodeBytes(String enc, CharsetDecoder decoder)
|
||||||
|
{
|
||||||
|
Matcher matcher = encodedChars.matcher(enc);
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
ByteBuffer bytes = ByteBuffer.allocate(enc.length() / 3);
|
||||||
|
while (matcher.find())
|
||||||
|
{
|
||||||
|
int b = Integer.parseInt(matcher.group(1), 16);
|
||||||
|
bytes.put((byte) b);
|
||||||
|
}
|
||||||
|
bytes.flip();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return decoder.decode(bytes).toString();
|
||||||
|
}
|
||||||
|
catch (CharacterCodingException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode '%' if it is not an encoding sequence
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodeNonCodes(String string)
|
||||||
|
{
|
||||||
|
Matcher matcher = nonCodes.matcher(string);
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
|
||||||
|
|
||||||
|
// FYI: we do not use the no-arg matcher.find()
|
||||||
|
// coupled with matcher.appendReplacement()
|
||||||
|
// because the matched text may contain
|
||||||
|
// a second % and we must make sure we
|
||||||
|
// encode it (if necessary).
|
||||||
|
int idx = 0;
|
||||||
|
while (matcher.find(idx))
|
||||||
|
{
|
||||||
|
int start = matcher.start();
|
||||||
|
buf.append(string.substring(idx, start));
|
||||||
|
buf.append("%25");
|
||||||
|
idx = start + 1;
|
||||||
|
}
|
||||||
|
buf.append(string.substring(idx));
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean savePathParams(String segment, StringBuffer newSegment, List<String> params)
|
||||||
|
{
|
||||||
|
boolean foundParam = false;
|
||||||
|
// Regular expressions can have '{' and '}' characters. Replace them to do match
|
||||||
|
segment = PathHelper.replaceEnclosedCurlyBraces(segment);
|
||||||
|
Matcher matcher = PathHelper.URI_TEMPLATE_PATTERN.matcher(segment);
|
||||||
|
while (matcher.find())
|
||||||
|
{
|
||||||
|
foundParam = true;
|
||||||
|
String group = matcher.group();
|
||||||
|
// Regular expressions can have '{' and '}' characters. Recover earlier replacement
|
||||||
|
params.add(PathHelper.recoverEnclosedCurlyBraces(group));
|
||||||
|
matcher.appendReplacement(newSegment, "_resteasy_uri_parameter");
|
||||||
|
}
|
||||||
|
matcher.appendTail(newSegment);
|
||||||
|
return foundParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep encoded values "%..." and template parameters intact i.e. "{x}"
|
||||||
|
*
|
||||||
|
* @param segment
|
||||||
|
* @param encoding
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodeValue(String segment, String[] encoding)
|
||||||
|
{
|
||||||
|
ArrayList<String> params = new ArrayList<String>();
|
||||||
|
boolean foundParam = false;
|
||||||
|
StringBuffer newSegment = new StringBuffer();
|
||||||
|
if (savePathParams(segment, newSegment, params))
|
||||||
|
{
|
||||||
|
foundParam = true;
|
||||||
|
segment = newSegment.toString();
|
||||||
|
}
|
||||||
|
String result = encodeFromArray(segment, encoding, false);
|
||||||
|
result = encodeNonCodes(result);
|
||||||
|
segment = result;
|
||||||
|
if (foundParam)
|
||||||
|
{
|
||||||
|
segment = pathParamReplacement(segment, params);
|
||||||
|
}
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||||
|
* <p/>
|
||||||
|
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
|
* / "*" / "+" / "," / ";" / "="
|
||||||
|
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
|
*/
|
||||||
|
public static String encodePathAsIs(String segment)
|
||||||
|
{
|
||||||
|
return encodeFromArray(segment, pathEncoding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||||
|
*
|
||||||
|
* @param segment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodePathSaveEncodings(String segment)
|
||||||
|
{
|
||||||
|
String result = encodeFromArray(segment, pathEncoding, false);
|
||||||
|
result = encodeNonCodes(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode via <a href="http://ietf.org/rfc/rfc3986.txt">RFC 3986</a>. PCHAR is allowed allong with '/'
|
||||||
|
* <p/>
|
||||||
|
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
|
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
|
* / "*" / "+" / "," / ";" / "="
|
||||||
|
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
|
*/
|
||||||
|
public static String encodePathSegmentAsIs(String segment)
|
||||||
|
{
|
||||||
|
return encodeFromArray(segment, pathSegmentEncoding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||||
|
*
|
||||||
|
* @param segment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodePathSegmentSaveEncodings(String segment)
|
||||||
|
{
|
||||||
|
String result = encodeFromArray(segment, pathSegmentEncoding, false);
|
||||||
|
result = encodeNonCodes(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes everything of a query parameter name or value.
|
||||||
|
*
|
||||||
|
* @param nameOrValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodeQueryParamAsIs(String nameOrValue)
|
||||||
|
{
|
||||||
|
return encodeFromArray(nameOrValue, queryNameValueEncoding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep any valid encodings from string i.e. keep "%2D" but don't keep "%p"
|
||||||
|
*
|
||||||
|
* @param segment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String encodeQueryParamSaveEncodings(String segment)
|
||||||
|
{
|
||||||
|
String result = encodeFromArray(segment, queryNameValueEncoding, false);
|
||||||
|
result = encodeNonCodes(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeFragmentAsIs(String nameOrValue)
|
||||||
|
{
|
||||||
|
return encodeFromArray(nameOrValue, queryNameValueEncoding, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String encodeFromArray(String segment, String[] encodingMap, boolean encodePercent)
|
||||||
|
{
|
||||||
|
StringBuffer result = new StringBuffer();
|
||||||
|
for (int i = 0; i < segment.length(); i++)
|
||||||
|
{
|
||||||
|
if (!encodePercent && segment.charAt(i) == '%')
|
||||||
|
{
|
||||||
|
result.append(segment.charAt(i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int idx = segment.charAt(i);
|
||||||
|
String encoding = encode(idx, encodingMap);
|
||||||
|
if (encoding == null)
|
||||||
|
{
|
||||||
|
result.append(segment.charAt(i));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.append(encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param zhar integer representation of character
|
||||||
|
* @param encodingMap encoding map
|
||||||
|
* @return URL encoded character
|
||||||
|
*/
|
||||||
|
private static String encode(int zhar, String[] encodingMap)
|
||||||
|
{
|
||||||
|
String encoded;
|
||||||
|
if (zhar < encodingMap.length)
|
||||||
|
{
|
||||||
|
encoded = encodingMap[zhar];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
encoded = URLEncoder.encode(Character.toString((char) zhar), UTF_8);
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String pathParamReplacement(String segment, List<String> params)
|
||||||
|
{
|
||||||
|
StringBuffer newSegment = new StringBuffer();
|
||||||
|
Matcher matcher = PARAM_REPLACEMENT.matcher(segment);
|
||||||
|
int i = 0;
|
||||||
|
while (matcher.find())
|
||||||
|
{
|
||||||
|
String replacement = params.get(i++);
|
||||||
|
// double encode slashes, so that slashes stay where they are
|
||||||
|
replacement = replacement.replace("\\", "\\\\");
|
||||||
|
replacement = replacement.replace("$", "\\$");
|
||||||
|
matcher.appendReplacement(newSegment, replacement);
|
||||||
|
}
|
||||||
|
matcher.appendTail(newSegment);
|
||||||
|
segment = newSegment.toString();
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* decode an encoded map
|
||||||
|
*
|
||||||
|
* @param map
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static MultivaluedHashMap<String, String> decode(MultivaluedHashMap<String, String> map)
|
||||||
|
{
|
||||||
|
MultivaluedHashMap<String, String> decoded = new MultivaluedHashMap<String, String>();
|
||||||
|
for (Map.Entry<String, List<String>> entry : map.entrySet())
|
||||||
|
{
|
||||||
|
List<String> values = entry.getValue();
|
||||||
|
for (String value : values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
decoded.add(URLDecoder.decode(entry.getKey(), UTF_8), URLDecoder.decode(value, UTF_8));
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultivaluedHashMap<String, String> encode(MultivaluedHashMap<String, String> map)
|
||||||
|
{
|
||||||
|
MultivaluedHashMap<String, String> decoded = new MultivaluedHashMap<String, String>();
|
||||||
|
for (Map.Entry<String, List<String>> entry : map.entrySet())
|
||||||
|
{
|
||||||
|
List<String> values = entry.getValue();
|
||||||
|
for (String value : values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
decoded.add(URLEncoder.encode(entry.getKey(), UTF_8), URLEncoder.encode(value, UTF_8));
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String decode(String string)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return URLDecoder.decode(string, UTF_8);
|
||||||
|
}
|
||||||
|
catch (UnsupportedEncodingException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
717
core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
Executable file
717
core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
Executable file
|
@ -0,0 +1,717 @@
|
||||||
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
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 KeycloakUriBuilder {
|
||||||
|
|
||||||
|
private String host;
|
||||||
|
private String scheme;
|
||||||
|
private int port = -1;
|
||||||
|
|
||||||
|
private String userInfo;
|
||||||
|
private String path;
|
||||||
|
private String query;
|
||||||
|
private String fragment;
|
||||||
|
private String ssp;
|
||||||
|
private String authority;
|
||||||
|
|
||||||
|
public static KeycloakUriBuilder fromUri(URI uri) {
|
||||||
|
return new KeycloakUriBuilder().uri(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeycloakUriBuilder fromUri(String uriTemplate) {
|
||||||
|
return new KeycloakUriBuilder().uri(uriTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static KeycloakUriBuilder fromPath(String path) throws IllegalArgumentException {
|
||||||
|
return new KeycloakUriBuilder().path(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public KeycloakUriBuilder clone() {
|
||||||
|
KeycloakUriBuilder impl = new KeycloakUriBuilder();
|
||||||
|
impl.host = host;
|
||||||
|
impl.scheme = scheme;
|
||||||
|
impl.port = port;
|
||||||
|
impl.userInfo = userInfo;
|
||||||
|
impl.path = path;
|
||||||
|
impl.query = query;
|
||||||
|
impl.fragment = fragment;
|
||||||
|
impl.ssp = ssp;
|
||||||
|
impl.authority = authority;
|
||||||
|
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Pattern opaqueUri = Pattern.compile("^([^:/?#]+):([^/].*)");
|
||||||
|
public static final Pattern hierarchicalUri = Pattern.compile("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?");
|
||||||
|
private static final Pattern hostPortPattern = Pattern.compile("([^/:]+):(\\d+)");
|
||||||
|
|
||||||
|
public static boolean compare(String s1, String s2) {
|
||||||
|
if (s1 == s2) return true;
|
||||||
|
if (s1 == null || s2 == null) return false;
|
||||||
|
return s1.equals(s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static URI relativize(URI from, URI to) {
|
||||||
|
if (!compare(from.getScheme(), to.getScheme())) return to;
|
||||||
|
if (!compare(from.getHost(), to.getHost())) return to;
|
||||||
|
if (from.getPort() != to.getPort()) return to;
|
||||||
|
if (from.getPath() == null && to.getPath() == null) return URI.create("");
|
||||||
|
else if (from.getPath() == null) return URI.create(to.getPath());
|
||||||
|
else if (to.getPath() == null) return to;
|
||||||
|
|
||||||
|
|
||||||
|
String fromPath = from.getPath();
|
||||||
|
if (fromPath.startsWith("/")) fromPath = fromPath.substring(1);
|
||||||
|
String[] fsplit = fromPath.split("/");
|
||||||
|
String toPath = to.getPath();
|
||||||
|
if (toPath.startsWith("/")) toPath = toPath.substring(1);
|
||||||
|
String[] tsplit = toPath.split("/");
|
||||||
|
|
||||||
|
int f = 0;
|
||||||
|
|
||||||
|
for (; f < fsplit.length && f < tsplit.length; f++) {
|
||||||
|
if (!fsplit[f].equals(tsplit[f])) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromPath("");
|
||||||
|
for (int i = f; i < fsplit.length; i++) builder.path("..");
|
||||||
|
for (int i = f; i < tsplit.length; i++) builder.path(tsplit[i]);
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may put path parameters anywhere within the uriTemplate except port
|
||||||
|
*
|
||||||
|
* @param uriTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static KeycloakUriBuilder fromTemplate(String uriTemplate) {
|
||||||
|
KeycloakUriBuilder impl = new KeycloakUriBuilder();
|
||||||
|
impl.uriTemplate(uriTemplate);
|
||||||
|
return impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You may put path parameters anywhere within the uriTemplate except port
|
||||||
|
*
|
||||||
|
* @param uriTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public KeycloakUriBuilder uriTemplate(String uriTemplate) {
|
||||||
|
if (uriTemplate == null) throw new IllegalArgumentException("uriTemplate parameter is null");
|
||||||
|
Matcher opaque = opaqueUri.matcher(uriTemplate);
|
||||||
|
if (opaque.matches()) {
|
||||||
|
this.authority = null;
|
||||||
|
this.host = null;
|
||||||
|
this.port = -1;
|
||||||
|
this.userInfo = null;
|
||||||
|
this.query = null;
|
||||||
|
this.scheme = opaque.group(1);
|
||||||
|
this.ssp = opaque.group(2);
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
Matcher match = hierarchicalUri.matcher(uriTemplate);
|
||||||
|
if (match.matches()) {
|
||||||
|
ssp = null;
|
||||||
|
return parseHierarchicalUri(uriTemplate, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KeycloakUriBuilder parseHierarchicalUri(String uriTemplate, Matcher match) {
|
||||||
|
boolean scheme = match.group(2) != null;
|
||||||
|
if (scheme) this.scheme = match.group(2);
|
||||||
|
String authority = match.group(4);
|
||||||
|
if (authority != null) {
|
||||||
|
this.authority = null;
|
||||||
|
String host = match.group(4);
|
||||||
|
int at = host.indexOf('@');
|
||||||
|
if (at > -1) {
|
||||||
|
String user = host.substring(0, at);
|
||||||
|
host = host.substring(at + 1);
|
||||||
|
this.userInfo = user;
|
||||||
|
}
|
||||||
|
Matcher hostPortMatch = hostPortPattern.matcher(host);
|
||||||
|
if (hostPortMatch.matches()) {
|
||||||
|
this.host = hostPortMatch.group(1);
|
||||||
|
int val = 0;
|
||||||
|
try {
|
||||||
|
this.port = Integer.parseInt(hostPortMatch.group(2));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate, e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match.group(5) != null) {
|
||||||
|
String group = match.group(5);
|
||||||
|
if (!scheme && !"".equals(group) && !group.startsWith("/") && group.indexOf(':') > -1)
|
||||||
|
throw new IllegalArgumentException("Illegal uri template: " + uriTemplate);
|
||||||
|
if (!"".equals(group)) replacePath(group);
|
||||||
|
}
|
||||||
|
if (match.group(7) != null) replaceQuery(match.group(7));
|
||||||
|
if (match.group(9) != null) fragment(match.group(9));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder uri(String uriTemplate) throws IllegalArgumentException {
|
||||||
|
return uriTemplate(uriTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder uri(URI uri) throws IllegalArgumentException {
|
||||||
|
if (uri == null) throw new IllegalArgumentException("URI was null");
|
||||||
|
|
||||||
|
if (uri.getRawFragment() != null) fragment = uri.getRawFragment();
|
||||||
|
|
||||||
|
if (uri.isOpaque()) {
|
||||||
|
scheme = uri.getScheme();
|
||||||
|
ssp = uri.getRawSchemeSpecificPart();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getScheme() == null) {
|
||||||
|
if (ssp != null) {
|
||||||
|
if (uri.getRawSchemeSpecificPart() != null) {
|
||||||
|
ssp = uri.getRawSchemeSpecificPart();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scheme = uri.getScheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
ssp = null;
|
||||||
|
if (uri.getRawAuthority() != null) {
|
||||||
|
if (uri.getRawUserInfo() == null && uri.getHost() == null && uri.getPort() == -1) {
|
||||||
|
authority = uri.getRawAuthority();
|
||||||
|
userInfo = null;
|
||||||
|
host = null;
|
||||||
|
port = -1;
|
||||||
|
} else {
|
||||||
|
authority = null;
|
||||||
|
if (uri.getRawUserInfo() != null) {
|
||||||
|
userInfo = uri.getRawUserInfo();
|
||||||
|
}
|
||||||
|
if (uri.getHost() != null) {
|
||||||
|
host = uri.getHost();
|
||||||
|
}
|
||||||
|
if (uri.getPort() != -1) {
|
||||||
|
port = uri.getPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri.getRawPath() != null && uri.getRawPath().length() > 0) {
|
||||||
|
path = uri.getRawPath();
|
||||||
|
}
|
||||||
|
if (uri.getRawQuery() != null && uri.getRawQuery().length() > 0) {
|
||||||
|
query = uri.getRawQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder scheme(String scheme) throws IllegalArgumentException {
|
||||||
|
this.scheme = scheme;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder schemeSpecificPart(String ssp) throws IllegalArgumentException {
|
||||||
|
if (ssp == null) throw new IllegalArgumentException("schemeSpecificPart was null");
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (scheme != null) sb.append(scheme).append(':');
|
||||||
|
if (ssp != null)
|
||||||
|
sb.append(ssp);
|
||||||
|
if (fragment != null && fragment.length() > 0) sb.append('#').append(fragment);
|
||||||
|
URI uri = URI.create(sb.toString());
|
||||||
|
|
||||||
|
if (uri.getRawSchemeSpecificPart() != null && uri.getRawPath() == null) {
|
||||||
|
this.ssp = uri.getRawSchemeSpecificPart();
|
||||||
|
} else {
|
||||||
|
this.ssp = null;
|
||||||
|
userInfo = uri.getRawUserInfo();
|
||||||
|
host = uri.getHost();
|
||||||
|
port = uri.getPort();
|
||||||
|
path = uri.getRawPath();
|
||||||
|
query = uri.getRawQuery();
|
||||||
|
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder userInfo(String ui) {
|
||||||
|
this.userInfo = ui;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder host(String host) throws IllegalArgumentException {
|
||||||
|
if (host != null && host.equals("")) throw new IllegalArgumentException("invalid host");
|
||||||
|
this.host = host;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder port(int port) throws IllegalArgumentException {
|
||||||
|
if (port < -1) throw new IllegalArgumentException("Invalid port value");
|
||||||
|
this.port = port;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String paths(boolean encode, String basePath, String... segments) {
|
||||||
|
String path = basePath;
|
||||||
|
if (path == null) path = "";
|
||||||
|
for (String segment : segments) {
|
||||||
|
if ("".equals(segment)) continue;
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
if (segment.startsWith("/")) {
|
||||||
|
segment = segment.substring(1);
|
||||||
|
if ("".equals(segment)) continue;
|
||||||
|
}
|
||||||
|
if (encode) segment = Encode.encodePath(segment);
|
||||||
|
path += segment;
|
||||||
|
} else {
|
||||||
|
if (encode) segment = Encode.encodePath(segment);
|
||||||
|
if ("".equals(path)) {
|
||||||
|
path = segment;
|
||||||
|
} else if (segment.startsWith("/")) {
|
||||||
|
path += segment;
|
||||||
|
} else {
|
||||||
|
path += "/" + segment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder path(String segment) throws IllegalArgumentException {
|
||||||
|
if (segment == null) throw new IllegalArgumentException("path was null");
|
||||||
|
path = paths(true, path, segment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder replaceMatrix(String matrix) throws IllegalArgumentException {
|
||||||
|
if (matrix == null) matrix = "";
|
||||||
|
if (!matrix.startsWith(";")) matrix = ";" + matrix;
|
||||||
|
matrix = Encode.encodePath(matrix);
|
||||||
|
if (path == null) {
|
||||||
|
path = matrix;
|
||||||
|
} else {
|
||||||
|
int start = path.lastIndexOf('/');
|
||||||
|
if (start < 0) start = 0;
|
||||||
|
int matrixIndex = path.indexOf(';', start);
|
||||||
|
if (matrixIndex > -1) path = path.substring(0, matrixIndex) + matrix;
|
||||||
|
else path += matrix;
|
||||||
|
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder replaceQuery(String query) throws IllegalArgumentException {
|
||||||
|
if (query == null || query.length() == 0) {
|
||||||
|
this.query = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.query = Encode.encodeQueryString(query);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder fragment(String fragment) throws IllegalArgumentException {
|
||||||
|
if (fragment == null) {
|
||||||
|
this.fragment = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.fragment = Encode.encodeFragment(fragment);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only replace path params in path of URI. This changes state of URIBuilder.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @param value
|
||||||
|
* @param isEncoded
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public KeycloakUriBuilder substitutePathParam(String name, Object value, boolean isEncoded) {
|
||||||
|
if (path != null) {
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
replacePathParameter(name, value.toString(), isEncoded, path, buffer, false);
|
||||||
|
path = buffer.toString();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI buildFromMap(Map<String, ? extends Object> values) throws IllegalArgumentException {
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
return buildUriFromMap(values, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI buildFromEncodedMap(Map<String, ? extends Object> values) throws IllegalArgumentException {
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
return buildUriFromMap(values, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI buildFromMap(Map<String, ?> values, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
return buildUriFromMap(values, false, encodeSlashInPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URI buildUriFromMap(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean encodeSlash) throws IllegalArgumentException {
|
||||||
|
String buf = buildString(paramMap, fromEncodedMap, false, encodeSlash);
|
||||||
|
try {
|
||||||
|
return URI.create(buf);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create URI: " + buf, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildString(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, boolean encodeSlash) {
|
||||||
|
for (Map.Entry<String, ? extends Object> entry : paramMap.entrySet()) {
|
||||||
|
if (entry.getKey() == null) throw new IllegalArgumentException("map key is null");
|
||||||
|
if (entry.getValue() == null) throw new IllegalArgumentException("map value is null");
|
||||||
|
}
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
|
||||||
|
if (scheme != null)
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, scheme, buffer, encodeSlash).append(":");
|
||||||
|
if (ssp != null) {
|
||||||
|
buffer.append(ssp);
|
||||||
|
} else if (userInfo != null || host != null || port != -1) {
|
||||||
|
buffer.append("//");
|
||||||
|
if (userInfo != null)
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, userInfo, buffer, encodeSlash).append("@");
|
||||||
|
if (host != null) {
|
||||||
|
if ("".equals(host)) throw new RuntimeException("empty host name");
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, host, buffer, encodeSlash);
|
||||||
|
}
|
||||||
|
if (port != -1) buffer.append(":").append(Integer.toString(port));
|
||||||
|
} else if (authority != null) {
|
||||||
|
buffer.append("//");
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, authority, buffer, encodeSlash);
|
||||||
|
}
|
||||||
|
if (path != null) {
|
||||||
|
StringBuffer tmp = new StringBuffer();
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, path, tmp, encodeSlash);
|
||||||
|
String tmpPath = tmp.toString();
|
||||||
|
if (userInfo != null || host != null) {
|
||||||
|
if (!tmpPath.startsWith("/")) buffer.append("/");
|
||||||
|
}
|
||||||
|
buffer.append(tmpPath);
|
||||||
|
}
|
||||||
|
if (query != null) {
|
||||||
|
buffer.append("?");
|
||||||
|
replaceQueryStringParameter(paramMap, fromEncodedMap, isTemplate, query, buffer);
|
||||||
|
}
|
||||||
|
if (fragment != null) {
|
||||||
|
buffer.append("#");
|
||||||
|
replaceParameter(paramMap, fromEncodedMap, isTemplate, fragment, buffer, encodeSlash);
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuffer replacePathParameter(String name, String value, boolean isEncoded, String string, StringBuffer buffer, boolean encodeSlash) {
|
||||||
|
Matcher matcher = createUriParamMatcher(string);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String param = matcher.group(1);
|
||||||
|
if (!param.equals(name)) continue;
|
||||||
|
if (!isEncoded) {
|
||||||
|
if (encodeSlash) value = Encode.encodePath(value);
|
||||||
|
else value = Encode.encodePathSegment(value);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
value = Encode.encodeNonCodes(value);
|
||||||
|
}
|
||||||
|
// if there is a $ then we must backslash it or it will screw up regex group substitution
|
||||||
|
value = value.replace("$", "\\$");
|
||||||
|
matcher.appendReplacement(buffer, value);
|
||||||
|
}
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Matcher createUriParamMatcher(String string) {
|
||||||
|
Matcher matcher = PathHelper.URI_PARAM_PATTERN.matcher(PathHelper.replaceEnclosedCurlyBraces(string));
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuffer replaceParameter(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, String string, StringBuffer buffer, boolean encodeSlash) {
|
||||||
|
Matcher matcher = createUriParamMatcher(string);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String param = matcher.group(1);
|
||||||
|
Object valObj = paramMap.get(param);
|
||||||
|
if (valObj == null && !isTemplate) {
|
||||||
|
throw new IllegalArgumentException("NULL value for template parameter: " + param);
|
||||||
|
} else if (valObj == null && isTemplate) {
|
||||||
|
matcher.appendReplacement(buffer, matcher.group());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String value = valObj.toString();
|
||||||
|
if (value != null) {
|
||||||
|
if (!fromEncodedMap) {
|
||||||
|
if (encodeSlash) value = Encode.encodePathSegmentAsIs(value);
|
||||||
|
else value = Encode.encodePathAsIs(value);
|
||||||
|
} else {
|
||||||
|
if (encodeSlash) value = Encode.encodePathSegmentSaveEncodings(value);
|
||||||
|
else value = Encode.encodePathSaveEncodings(value);
|
||||||
|
}
|
||||||
|
matcher.appendReplacement(buffer, Matcher.quoteReplacement(value));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("path param " + param + " has not been provided by the parameter map");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected StringBuffer replaceQueryStringParameter(Map<String, ? extends Object> paramMap, boolean fromEncodedMap, boolean isTemplate, String string, StringBuffer buffer) {
|
||||||
|
Matcher matcher = createUriParamMatcher(string);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String param = matcher.group(1);
|
||||||
|
Object valObj = paramMap.get(param);
|
||||||
|
if (valObj == null && !isTemplate) {
|
||||||
|
throw new IllegalArgumentException("NULL value for template parameter: " + param);
|
||||||
|
} else if (valObj == null && isTemplate) {
|
||||||
|
matcher.appendReplacement(buffer, matcher.group());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String value = valObj.toString();
|
||||||
|
if (value != null) {
|
||||||
|
if (!fromEncodedMap) {
|
||||||
|
value = Encode.encodeQueryParamAsIs(value);
|
||||||
|
} else {
|
||||||
|
value = Encode.encodeQueryParamSaveEncodings(value);
|
||||||
|
}
|
||||||
|
matcher.appendReplacement(buffer, value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("path param " + param + " has not been provided by the parameter map");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matcher.appendTail(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a unique order list of path params
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getPathParamNamesInDeclarationOrder() {
|
||||||
|
List<String> params = new ArrayList<String>();
|
||||||
|
HashSet<String> set = new HashSet<String>();
|
||||||
|
if (scheme != null) addToPathParamList(params, set, scheme);
|
||||||
|
if (userInfo != null) addToPathParamList(params, set, userInfo);
|
||||||
|
if (host != null) addToPathParamList(params, set, host);
|
||||||
|
if (path != null) addToPathParamList(params, set, path);
|
||||||
|
if (query != null) addToPathParamList(params, set, query);
|
||||||
|
if (fragment != null) addToPathParamList(params, set, fragment);
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToPathParamList(List<String> params, HashSet<String> set, String string) {
|
||||||
|
Matcher matcher = PathHelper.URI_PARAM_PATTERN.matcher(PathHelper.replaceEnclosedCurlyBraces(string));
|
||||||
|
while (matcher.find()) {
|
||||||
|
String param = matcher.group(1);
|
||||||
|
if (set.contains(param)) continue;
|
||||||
|
else {
|
||||||
|
set.add(param);
|
||||||
|
params.add(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI build(Object... values) throws IllegalArgumentException {
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
return buildFromValues(true, false, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected URI buildFromValues(boolean encodeSlash, boolean encoded, Object... values) {
|
||||||
|
List<String> params = getPathParamNamesInDeclarationOrder();
|
||||||
|
if (values.length < params.size())
|
||||||
|
throw new IllegalArgumentException("You did not supply enough values to fill path parameters");
|
||||||
|
|
||||||
|
Map<String, Object> pathParams = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 0; i < params.size(); i++) {
|
||||||
|
String pathParam = params.get(i);
|
||||||
|
Object val = values[i];
|
||||||
|
if (val == null) throw new IllegalArgumentException("A value was null");
|
||||||
|
pathParams.put(pathParam, val.toString());
|
||||||
|
}
|
||||||
|
String buf = null;
|
||||||
|
try {
|
||||||
|
buf = buildString(pathParams, encoded, false, encodeSlash);
|
||||||
|
return new URI(buf);
|
||||||
|
//return URI.create(buf);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create URI: " + buf, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder matrixParam(String name, Object... values) throws IllegalArgumentException {
|
||||||
|
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
if (path == null) path = "";
|
||||||
|
for (Object val : values) {
|
||||||
|
if (val == null) throw new IllegalArgumentException("null value");
|
||||||
|
path += ";" + Encode.encodeMatrixParam(name) + "=" + Encode.encodeMatrixParam(val.toString());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern PARAM_REPLACEMENT = Pattern.compile("_resteasy_uri_parameter");
|
||||||
|
|
||||||
|
|
||||||
|
public KeycloakUriBuilder queryParam(String name, Object... values) throws IllegalArgumentException {
|
||||||
|
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||||
|
if (values == null) throw new IllegalArgumentException("values parameter is null");
|
||||||
|
for (Object value : values) {
|
||||||
|
if (value == null) throw new IllegalArgumentException("A passed in value was null");
|
||||||
|
if (query == null) query = "";
|
||||||
|
else query += "&";
|
||||||
|
query += Encode.encodeQueryParam(name) + "=" + Encode.encodeQueryParam(value.toString());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder replaceQueryParam(String name, Object... values) throws IllegalArgumentException {
|
||||||
|
if (name == null) throw new IllegalArgumentException("name parameter is null");
|
||||||
|
if (query == null || query.equals("")) {
|
||||||
|
if (values != null) return queryParam(name, values);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] params = query.split("&");
|
||||||
|
query = null;
|
||||||
|
|
||||||
|
String replacedName = Encode.encodeQueryParam(name);
|
||||||
|
|
||||||
|
|
||||||
|
for (String param : params) {
|
||||||
|
int pos = param.indexOf('=');
|
||||||
|
if (pos >= 0) {
|
||||||
|
String paramName = param.substring(0, pos);
|
||||||
|
if (paramName.equals(replacedName)) continue;
|
||||||
|
} else {
|
||||||
|
if (param.equals(replacedName)) continue;
|
||||||
|
}
|
||||||
|
if (query == null) query = "";
|
||||||
|
else query += "&";
|
||||||
|
query += param;
|
||||||
|
}
|
||||||
|
// don't set values if values is null
|
||||||
|
if (values == null) return this;
|
||||||
|
// don't set values if values is null
|
||||||
|
if (values == null) return this;
|
||||||
|
return queryParam(name, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScheme() {
|
||||||
|
return scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserInfo() {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQuery() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFragment() {
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder segment(String... segments) throws IllegalArgumentException {
|
||||||
|
if (segments == null) throw new IllegalArgumentException("segments parameter was null");
|
||||||
|
for (String segment : segments) {
|
||||||
|
if (segment == null) throw new IllegalArgumentException("A segment is null");
|
||||||
|
path(Encode.encodePathSegment(segment));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder replacePath(String path) {
|
||||||
|
if (path == null) {
|
||||||
|
this.path = null;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
this.path = Encode.encodePath(path);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public URI build(Object[] values, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||||
|
if (values == null) throw new IllegalArgumentException("values param is null");
|
||||||
|
return buildFromValues(encodeSlashInPath, false, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toTemplate() {
|
||||||
|
return buildString(new HashMap<String, Object>(), true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder resolveTemplate(String name, Object value) throws IllegalArgumentException {
|
||||||
|
if (name == null) throw new IllegalArgumentException("name param is null");
|
||||||
|
if (value == null) throw new IllegalArgumentException("value param is null");
|
||||||
|
HashMap<String, Object> vals = new HashMap<String, Object>();
|
||||||
|
vals.put(name, value);
|
||||||
|
return resolveTemplates(vals);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder resolveTemplates(Map<String, Object> templateValues) throws IllegalArgumentException {
|
||||||
|
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||||
|
String str = buildString(templateValues, false, true, true);
|
||||||
|
return fromTemplate(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder resolveTemplate(String name, Object value, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||||
|
if (name == null) throw new IllegalArgumentException("name param is null");
|
||||||
|
if (value == null) throw new IllegalArgumentException("value param is null");
|
||||||
|
HashMap<String, Object> vals = new HashMap<String, Object>();
|
||||||
|
vals.put(name, value);
|
||||||
|
String str = buildString(vals, false, true, encodeSlashInPath);
|
||||||
|
return fromTemplate(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder resolveTemplates(Map<String, Object> templateValues, boolean encodeSlashInPath) throws IllegalArgumentException {
|
||||||
|
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||||
|
String str = buildString(templateValues, false, true, encodeSlashInPath);
|
||||||
|
return fromTemplate(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder resolveTemplatesFromEncoded(Map<String, Object> templateValues) throws IllegalArgumentException {
|
||||||
|
if (templateValues == null) throw new IllegalArgumentException("templateValues param null");
|
||||||
|
String str = buildString(templateValues, true, true, true);
|
||||||
|
return fromTemplate(str);
|
||||||
|
}
|
||||||
|
}
|
62
core/src/main/java/org/keycloak/util/PathHelper.java
Executable file
62
core/src/main/java/org/keycloak/util/PathHelper.java
Executable file
|
@ -0,0 +1,62 @@
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for handling URI template parameters. As the Java
|
||||||
|
* regulare expressions package does not handle named groups, this
|
||||||
|
* class attempts to simulate that functionality by using groups.
|
||||||
|
*
|
||||||
|
* @author Ryan J. McDonough
|
||||||
|
* @author Bill Burke
|
||||||
|
* @since 1.0
|
||||||
|
* Nov 8, 2006
|
||||||
|
*/
|
||||||
|
public class PathHelper
|
||||||
|
{
|
||||||
|
public static final String URI_PARAM_NAME_REGEX = "\\w[\\w\\.-]*";
|
||||||
|
public static final String URI_PARAM_REGEX_REGEX = "[^{}][^{}]*";
|
||||||
|
public static final String URI_PARAM_REGEX = "\\{\\s*(" + URI_PARAM_NAME_REGEX + ")\\s*(:\\s*(" + URI_PARAM_REGEX_REGEX + "))?\\}";
|
||||||
|
public static final Pattern URI_PARAM_PATTERN = Pattern.compile(URI_PARAM_REGEX);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A regex pattern that searches for a URI template parameter in the form of {*}
|
||||||
|
*/
|
||||||
|
public static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("(\\{([^}]+)\\})");
|
||||||
|
|
||||||
|
public static final char openCurlyReplacement = 6;
|
||||||
|
public static final char closeCurlyReplacement = 7;
|
||||||
|
|
||||||
|
public static String replaceEnclosedCurlyBraces(String str)
|
||||||
|
{
|
||||||
|
char[] chars = str.toCharArray();
|
||||||
|
int open = 0;
|
||||||
|
for (int i = 0; i < chars.length; i++)
|
||||||
|
{
|
||||||
|
if (chars[i] == '{')
|
||||||
|
{
|
||||||
|
if (open != 0) chars[i] = openCurlyReplacement;
|
||||||
|
open++;
|
||||||
|
}
|
||||||
|
else if (chars[i] == '}')
|
||||||
|
{
|
||||||
|
open--;
|
||||||
|
if (open != 0)
|
||||||
|
{
|
||||||
|
chars[i] = closeCurlyReplacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String recoverEnclosedCurlyBraces(String str)
|
||||||
|
{
|
||||||
|
return str.replace(openCurlyReplacement, '{').replace(closeCurlyReplacement, '}');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,26 +28,6 @@
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-adapter</artifactId>
|
<artifactId>keycloak-as7-adapter</artifactId>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package org.jboss.resteasy.example.oauth;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
|
||||||
import org.keycloak.SkeletonKeySession;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.core.GenericType;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class CustomerDatabaseClient
|
|
||||||
{
|
|
||||||
public static List<String> getCustomers(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
|
||||||
ResteasyClient client = new ResteasyClientBuilder()
|
|
||||||
.trustStore(session.getMetadata().getTruststore())
|
|
||||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Response response = client.target("http://localhost:8080/database/customers").request()
|
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
|
|
||||||
return response.readEntity(new GenericType<List<String>>(){});
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.keycloak.example;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.keycloak.SkeletonKeySession;
|
||||||
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class CustomerDatabaseClient {
|
||||||
|
|
||||||
|
static class TypedList extends ArrayList<String> {}
|
||||||
|
|
||||||
|
public static List<String> getCustomers() {
|
||||||
|
SkeletonKeySession session = SkeletonKeySession.getContext();
|
||||||
|
HttpClient client = new HttpClientBuilder()
|
||||||
|
.trustStore(session.getMetadata().getTruststore())
|
||||||
|
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||||
|
try {
|
||||||
|
HttpGet get = new HttpGet("http://localhost:8080/database/customers");
|
||||||
|
get.addHeader("Authorization", "Bearer " + session.getTokenString());
|
||||||
|
try {
|
||||||
|
HttpResponse response = client.execute(get);
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(is, TypedList.class);
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
client.getConnectionManager().shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,6 @@
|
||||||
<deployment>
|
<deployment>
|
||||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.bouncycastle"/>
|
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
|
|
||||||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</deployment>
|
</deployment>
|
||||||
</jboss-deployment-structure>
|
</jboss-deployment-structure>
|
|
@ -1,5 +1,5 @@
|
||||||
<jboss-web>
|
<jboss-web>
|
||||||
<valve>
|
<valve>
|
||||||
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
<class-name>org.keycloak.adapters.as7.OAuthAuthenticatorValve</class-name>
|
||||||
</valve>
|
</valve>
|
||||||
</jboss-web>
|
</jboss-web>
|
2
examples/as7-eap-demo/customer-app/src/main/webapp/admin/admin.jsp
Normal file → Executable file
2
examples/as7-eap-demo/customer-app/src/main/webapp/admin/admin.jsp
Normal file → Executable file
|
@ -2,7 +2,7 @@
|
||||||
pageEncoding="ISO-8859-1"%>
|
pageEncoding="ISO-8859-1"%>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Customer Admin Iterface</title>
|
<title>Customer Admin Interface</title>
|
||||||
</head>
|
</head>
|
||||||
<body bgcolor="#E3F6CE">
|
<body bgcolor="#E3F6CE">
|
||||||
<h1>Customer Admin Interface</h1>
|
<h1>Customer Admin Interface</h1>
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
|
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||||
pageEncoding="ISO-8859-1"%>
|
pageEncoding="ISO-8859-1"%>
|
||||||
|
<%@ page import="org.keycloak.example.CustomerDatabaseClient" %>
|
||||||
|
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Customer View Page</title>
|
<title>Customer View Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body bgcolor="#E3F6CE">
|
<body bgcolor="#E3F6CE">
|
||||||
<%
|
<%
|
||||||
String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||||
.queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
|
.queryParam("redirect_uri", "http://localhost:8080/customer-portal").build().toString();
|
||||||
String acctUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/account").build().toString();
|
String acctUri = "http://localhost:8080/auth-server/rest/realms/demo/account";
|
||||||
%>
|
%>
|
||||||
<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
<p>Goto: <a href="http://localhost:8080/product-portal">products</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
||||||
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
||||||
<h2>Customer Listing</h2>
|
<h2>Customer Listing</h2>
|
||||||
<%
|
<%
|
||||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.CustomerDatabaseClient.getCustomers(request);
|
java.util.List<String> list = CustomerDatabaseClient.getCustomers();
|
||||||
for (String cust : list)
|
for (String cust : list)
|
||||||
{
|
{
|
||||||
out.print("<p>");
|
out.print("<p>");
|
||||||
|
|
|
@ -30,24 +30,23 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
<version>${project.version}</version>
|
<scope>provided</scope>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-adapter</artifactId>
|
<artifactId>keycloak-as7-adapter</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
|
<!--
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
-->
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<deployment>
|
<deployment>
|
||||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.bouncycastle"/>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</deployment>
|
</deployment>
|
||||||
</jboss-deployment-structure>
|
</jboss-deployment-structure>
|
|
@ -28,26 +28,6 @@
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-as7-adapter</artifactId>
|
<artifactId>keycloak-as7-adapter</artifactId>
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package org.jboss.resteasy.example.oauth;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
|
||||||
import org.keycloak.SkeletonKeySession;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.core.GenericType;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class ProductDatabaseClient
|
|
||||||
{
|
|
||||||
public static List<String> getProducts(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
SkeletonKeySession session = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
|
||||||
ResteasyClient client = new ResteasyClientBuilder()
|
|
||||||
.trustStore(session.getMetadata().getTruststore())
|
|
||||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Response response = client.target("http://localhost:8080/database/products").request()
|
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + session.getTokenString()).get();
|
|
||||||
return response.readEntity(new GenericType<List<String>>(){});
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.keycloak.example.oauth;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.keycloak.SkeletonKeySession;
|
||||||
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ProductDatabaseClient
|
||||||
|
{
|
||||||
|
static class TypedList extends ArrayList<String> {}
|
||||||
|
|
||||||
|
public static List<String> getProducts() {
|
||||||
|
SkeletonKeySession session = SkeletonKeySession.getContext();
|
||||||
|
HttpClient client = new HttpClientBuilder()
|
||||||
|
.trustStore(session.getMetadata().getTruststore())
|
||||||
|
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY).build();
|
||||||
|
try {
|
||||||
|
HttpGet get = new HttpGet("http://localhost:8080/database/products");
|
||||||
|
get.addHeader("Authorization", "Bearer " + session.getTokenString());
|
||||||
|
try {
|
||||||
|
HttpResponse response = client.execute(get);
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(is, TypedList.class);
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
client.getConnectionManager().shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<jboss-web>
|
<jboss-web>
|
||||||
<valve>
|
<valve>
|
||||||
<class-name>org.keycloak.adapters.as7.OAuthManagedResourceValve</class-name>
|
<class-name>org.keycloak.adapters.as7.OAuthAuthenticatorValve</class-name>
|
||||||
</valve>
|
</valve>
|
||||||
</jboss-web>
|
</jboss-web>
|
|
@ -1,21 +1,23 @@
|
||||||
<%@ page import="javax.ws.rs.core.UriBuilder" language="java" contentType="text/html; charset=ISO-8859-1"
|
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||||
pageEncoding="ISO-8859-1"%>
|
pageEncoding="ISO-8859-1"%>
|
||||||
|
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %>
|
||||||
|
<%@ page import="org.keycloak.util.KeycloakUriBuilder" %>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Product View Page</title>
|
<title>Product View Page</title>
|
||||||
</head>
|
</head>
|
||||||
<body bgcolor="#F5F6CE">
|
<body bgcolor="#F5F6CE">
|
||||||
<%
|
<%
|
||||||
String logoutUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/tokens/logout")
|
||||||
.queryParam("redirect_uri", "http://localhost:8080/product-portal").build().toString();
|
.queryParam("redirect_uri", "http://localhost:8080/product-portal").build().toString();
|
||||||
String acctUri = UriBuilder.fromUri("http://localhost:8080/auth-server/rest/realms/demo/account").build().toString();
|
String acctUri = "http://localhost:8080/auth-server/rest/realms/demo/account";
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<p>Goto: <a href="http://localhost:8080/customer-portal">customers</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
<p>Goto: <a href="http://localhost:8080/customer-portal">customers</a> | <a href="<%=logoutUri%>">logout</a> | <a href="<%=acctUri%>">manage acct</a></p>
|
||||||
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
User <b><%=request.getUserPrincipal().getName()%></b> made this request.
|
||||||
<h2>Product Listing</h2>
|
<h2>Product Listing</h2>
|
||||||
<%
|
<%
|
||||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
|
java.util.List<String> list = ProductDatabaseClient.getProducts();
|
||||||
for (String cust : list)
|
for (String cust : list)
|
||||||
{
|
{
|
||||||
out.print("<p>");
|
out.print("<p>");
|
||||||
|
|
12
examples/as7-eap-demo/third-party/pom.xml
vendored
12
examples/as7-eap-demo/third-party/pom.xml
vendored
|
@ -21,19 +21,9 @@
|
||||||
<version>1.0.1.Final</version>
|
<version>1.0.1.Final</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-servlet-oauth-client</artifactId>
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core-jaxrs</artifactId>
|
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.jboss.resteasy.example.oauth;
|
package org.keycloak.example.oauth;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
|
||||||
import org.keycloak.servlet.ServletOAuthClient;
|
import org.keycloak.servlet.ServletOAuthClient;
|
||||||
|
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
|
@ -55,7 +54,6 @@ public class Bootstrap implements ServletContextListener {
|
||||||
client.setPassword("password");
|
client.setPassword("password");
|
||||||
client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login");
|
client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login");
|
||||||
client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes");
|
client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes");
|
||||||
client.setClient(new ResteasyClientBuilder().build());
|
|
||||||
client.start();
|
client.start();
|
||||||
sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client);
|
sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client);
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
package org.jboss.resteasy.example.oauth;
|
package org.keycloak.example.oauth;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.apache.http.HttpEntity;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.keycloak.adapters.TokenGrantRequest;
|
||||||
import org.keycloak.servlet.ServletOAuthClient;
|
import org.keycloak.servlet.ServletOAuthClient;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.core.GenericType;
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +34,8 @@ public class ProductDatabaseClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class TypedList extends ArrayList<String> {}
|
||||||
|
|
||||||
public static List<String> getProducts(HttpServletRequest request) {
|
public static List<String> getProducts(HttpServletRequest request) {
|
||||||
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
|
// This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute
|
||||||
// that is set in the Bootstrap context listenr in this project.
|
// that is set in the Bootstrap context listenr in this project.
|
||||||
|
@ -38,32 +43,30 @@ public class ProductDatabaseClient {
|
||||||
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
|
// and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code
|
||||||
// and take a look how it works.
|
// and take a look how it works.
|
||||||
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
|
ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName());
|
||||||
String token = oAuthClient.getBearerToken(request);
|
String token = null;
|
||||||
ResteasyClient client = new ResteasyClientBuilder()
|
|
||||||
.trustStore(oAuthClient.getTruststore())
|
|
||||||
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY).build();
|
|
||||||
try {
|
try {
|
||||||
// invoke without the Authorization header
|
token = oAuthClient.getBearerToken(request);
|
||||||
Response response = client.target("http://localhost:8080/database/products").request().get();
|
} catch (IOException e) {
|
||||||
response.close();
|
throw new RuntimeException(e);
|
||||||
if (response.getStatus() != 401) {
|
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||||
response.close();
|
throw new RuntimeException(failure);
|
||||||
client.close();
|
|
||||||
throw new RuntimeException("Expecting an auth status code: " + response.getStatus());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpClient client = oAuthClient.getClient();
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet("http://localhost:8080/database/products");
|
||||||
|
get.addHeader("Authorization", "Bearer " + token);
|
||||||
try {
|
try {
|
||||||
Response response = client.target("http://localhost:8080/database/products").request()
|
HttpResponse response = client.execute(get);
|
||||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token).get();
|
HttpEntity entity = response.getEntity();
|
||||||
if (response.getStatus() != 200) {
|
InputStream is = entity.getContent();
|
||||||
response.close();
|
try {
|
||||||
throw new RuntimeException("Failed to access!: " + response.getStatus());
|
return JsonSerialization.readValue(is, TypedList.class);
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
}
|
}
|
||||||
return response.readEntity(new GenericType<List<String>>() {
|
} catch (IOException e) {
|
||||||
});
|
throw new RuntimeException(e);
|
||||||
} finally {
|
|
||||||
client.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,8 +2,6 @@
|
||||||
<deployment>
|
<deployment>
|
||||||
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
<!-- This allows you to define additional dependencies, it is the same as using the Dependencies: manifest attribute -->
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.jboss.resteasy.resteasy-jaxrs" services="import"/>
|
|
||||||
<module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</deployment>
|
</deployment>
|
||||||
</jboss-deployment-structure>
|
</jboss-deployment-structure>
|
|
@ -7,7 +7,7 @@
|
||||||
<module-name>oauth-client</module-name>
|
<module-name>oauth-client</module-name>
|
||||||
|
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>org.jboss.resteasy.example.oauth.Bootstrap</listener-class>
|
<listener-class>org.keycloak.example.oauth.Bootstrap</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
<!--
|
<!--
|
||||||
<security-constraint>
|
<security-constraint>
|
||||||
|
|
3
examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file → Executable file
3
examples/as7-eap-demo/third-party/src/main/webapp/pull_data.jsp
vendored
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
||||||
|
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %>
|
||||||
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
|
||||||
pageEncoding="ISO-8859-1"%>
|
pageEncoding="ISO-8859-1"%>
|
||||||
<html>
|
<html>
|
||||||
|
@ -7,7 +8,7 @@
|
||||||
<body>
|
<body>
|
||||||
<h2>Pulled Product Listing</h2>
|
<h2>Pulled Product Listing</h2>
|
||||||
<%
|
<%
|
||||||
java.util.List<String> list = org.jboss.resteasy.example.oauth.ProductDatabaseClient.getProducts(request);
|
java.util.List<String> list = ProductDatabaseClient.getProducts(request);
|
||||||
for (String prod : list)
|
for (String prod : list)
|
||||||
{
|
{
|
||||||
out.print("<p>");
|
out.print("<p>");
|
||||||
|
|
4
examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file → Executable file
4
examples/as7-eap-demo/third-party/src/main/webapp/redirect.jsp
vendored
Normal file → Executable file
|
@ -1,3 +1,3 @@
|
||||||
<%
|
<%@ page import="org.keycloak.example.oauth.ProductDatabaseClient" %><%
|
||||||
org.jboss.resteasy.example.oauth.ProductDatabaseClient.redirect(request, response);
|
ProductDatabaseClient.redirect(request, response);
|
||||||
%>
|
%>
|
|
@ -22,14 +22,17 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-core-asl</artifactId>
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-mapper-asl</artifactId>
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.jackson</groupId>
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
<artifactId>jackson-xc</artifactId>
|
<artifactId>jackson-xc</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
|
@ -37,13 +40,9 @@
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
<scope>provided</scope>
|
<version>4.1.2</version>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
import org.apache.http.conn.scheme.Scheme;
|
||||||
|
import org.apache.http.conn.scheme.SchemeRegistry;
|
||||||
|
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||||
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||||
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
|
import org.apache.http.impl.conn.SingleClientConnManager;
|
||||||
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
|
import org.apache.http.params.BasicHttpParams;
|
||||||
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLException;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
import javax.net.ssl.SSLSocket;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstraction for creating HttpClients. Allows SSL configuration.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class HttpClientBuilder {
|
||||||
|
public static enum HostnameVerificationPolicy {
|
||||||
|
/**
|
||||||
|
* Hostname verification is not done on the server's certificate
|
||||||
|
*/
|
||||||
|
ANY,
|
||||||
|
/**
|
||||||
|
* Allows wildcards in subdomain names i.e. *.foo.com
|
||||||
|
*/
|
||||||
|
WILDCARD,
|
||||||
|
/**
|
||||||
|
* CN must match hostname connecting to
|
||||||
|
*/
|
||||||
|
STRICT
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
private static class PassthroughTrustManager implements X509TrustManager {
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain,
|
||||||
|
String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain,
|
||||||
|
String authType) throws CertificateException {
|
||||||
|
}
|
||||||
|
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected KeyStore truststore;
|
||||||
|
protected KeyStore clientKeyStore;
|
||||||
|
protected String clientPrivateKeyPassword;
|
||||||
|
protected boolean disableTrustManager;
|
||||||
|
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
|
||||||
|
protected SSLContext sslContext;
|
||||||
|
protected int connectionPoolSize = 100;
|
||||||
|
protected int maxPooledPerRoute = 0;
|
||||||
|
protected long connectionTTL = -1;
|
||||||
|
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
|
||||||
|
protected HostnameVerifier verifier = null;
|
||||||
|
protected long socketTimeout = -1;
|
||||||
|
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
|
protected long establishConnectionTimeout = -1;
|
||||||
|
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket inactivity timeout
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
|
||||||
|
{
|
||||||
|
this.socketTimeout = timeout;
|
||||||
|
this.socketTimeoutUnits = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When trying to make an initial socket connection, what is the timeout?
|
||||||
|
*
|
||||||
|
* @param timeout
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
|
||||||
|
{
|
||||||
|
this.establishConnectionTimeout = timeout;
|
||||||
|
this.establishConnectionTimeoutUnits = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
|
||||||
|
this.connectionTTL = ttl;
|
||||||
|
this.connectionTTLUnit = unit;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
|
||||||
|
this.maxPooledPerRoute = maxPooledPerRoute;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
|
||||||
|
this.connectionPoolSize = connectionPoolSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
|
||||||
|
* hole, so only set this option if you cannot or do not want to verify the identity of the
|
||||||
|
* host you are communicating with.
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder disableTrustManager() {
|
||||||
|
this.disableTrustManager = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSL policy used to verify hostnames
|
||||||
|
*
|
||||||
|
* @param policy
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
|
||||||
|
this.policy = policy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HttpClientBuilder sslContext(SSLContext sslContext) {
|
||||||
|
this.sslContext = sslContext;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder trustStore(KeyStore truststore) {
|
||||||
|
this.truststore = truststore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
|
||||||
|
this.clientKeyStore = keyStore;
|
||||||
|
this.clientPrivateKeyPassword = password;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
|
||||||
|
this.clientKeyStore = keyStore;
|
||||||
|
this.clientPrivateKeyPassword = new String(password);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class VerifierWrapper implements X509HostnameVerifier {
|
||||||
|
protected HostnameVerifier verifier;
|
||||||
|
|
||||||
|
VerifierWrapper(HostnameVerifier verifier) {
|
||||||
|
this.verifier = verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, SSLSocket ssl) throws IOException {
|
||||||
|
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, X509Certificate cert) throws SSLException {
|
||||||
|
throw new SSLException("This verification path not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
|
||||||
|
throw new SSLException("This verification path not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean verify(String s, SSLSession sslSession) {
|
||||||
|
return verifier.verify(s, sslSession);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClient build() {
|
||||||
|
X509HostnameVerifier verifier = null;
|
||||||
|
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
|
||||||
|
else {
|
||||||
|
switch (policy) {
|
||||||
|
case ANY:
|
||||||
|
verifier = new AllowAllHostnameVerifier();
|
||||||
|
break;
|
||||||
|
case WILDCARD:
|
||||||
|
verifier = new BrowserCompatHostnameVerifier();
|
||||||
|
break;
|
||||||
|
case STRICT:
|
||||||
|
verifier = new StrictHostnameVerifier();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
SSLSocketFactory sslsf = null;
|
||||||
|
SSLContext theContext = sslContext;
|
||||||
|
if (disableTrustManager) {
|
||||||
|
theContext = SSLContext.getInstance("SSL");
|
||||||
|
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
|
||||||
|
new SecureRandom());
|
||||||
|
verifier = new AllowAllHostnameVerifier();
|
||||||
|
sslsf = new SSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (theContext != null) {
|
||||||
|
sslsf = new SSLSocketFactory(theContext, verifier);
|
||||||
|
} else if (clientKeyStore != null || truststore != null) {
|
||||||
|
sslsf = new SSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
|
||||||
|
} else {
|
||||||
|
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
|
||||||
|
tlsContext.init(null, null, null);
|
||||||
|
sslsf = new SSLSocketFactory(tlsContext, verifier);
|
||||||
|
}
|
||||||
|
SchemeRegistry registry = new SchemeRegistry();
|
||||||
|
registry.register(
|
||||||
|
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
|
||||||
|
Scheme httpsScheme = new Scheme("https", 443, sslsf);
|
||||||
|
registry.register(httpsScheme);
|
||||||
|
ClientConnectionManager cm = null;
|
||||||
|
if (connectionPoolSize > 0) {
|
||||||
|
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
|
||||||
|
tcm.setMaxTotal(connectionPoolSize);
|
||||||
|
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
|
||||||
|
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
|
||||||
|
cm = tcm;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cm = new SingleClientConnManager(registry);
|
||||||
|
}
|
||||||
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
|
if (socketTimeout > -1)
|
||||||
|
{
|
||||||
|
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
|
||||||
|
|
||||||
|
}
|
||||||
|
if (establishConnectionTimeout > -1)
|
||||||
|
{
|
||||||
|
HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
|
||||||
|
}
|
||||||
|
return new DefaultHttpClient(cm, params);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package org.keycloak.adapters;
|
||||||
|
|
||||||
|
import org.apache.http.HttpEntity;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class TokenGrantRequest {
|
||||||
|
|
||||||
|
public static class HttpFailure extends Exception {
|
||||||
|
private int status;
|
||||||
|
private String error;
|
||||||
|
|
||||||
|
public HttpFailure(int status, String error) {
|
||||||
|
this.status = status;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccessTokenResponse invoke(RealmConfiguration config, String code, String redirectUri) throws HttpFailure, IOException {
|
||||||
|
String codeUrl = config.getCodeUrl();
|
||||||
|
String client_id = config.getMetadata().getResourceName();
|
||||||
|
Map<String,String> credentials = config.getResourceCredentials();
|
||||||
|
HttpClient client = config.getClient();
|
||||||
|
|
||||||
|
return invoke(client, code, codeUrl, redirectUri, client_id, credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AccessTokenResponse invoke(HttpClient client, String code, String codeUrl, String redirectUri, String client_id, Map<String, String> credentials) throws IOException, HttpFailure {
|
||||||
|
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
|
||||||
|
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||||
|
String password = credentials.get("password");
|
||||||
|
formparams.add(new BasicNameValuePair("grant_type", "authorization_code"));
|
||||||
|
formparams.add(new BasicNameValuePair("code", code));
|
||||||
|
formparams.add(new BasicNameValuePair("client_id", client_id));
|
||||||
|
formparams.add(new BasicNameValuePair(CredentialRepresentation.PASSWORD, password));
|
||||||
|
formparams.add(new BasicNameValuePair("redirect_uri", redirectUri));
|
||||||
|
HttpResponse response = null;
|
||||||
|
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
|
||||||
|
HttpPost post = new HttpPost(codeUrl);
|
||||||
|
post.setEntity(form);
|
||||||
|
response = client.execute(post);
|
||||||
|
int status = response.getStatusLine().getStatusCode();
|
||||||
|
HttpEntity entity = response.getEntity();
|
||||||
|
if (status != 200) {
|
||||||
|
error(status, entity);
|
||||||
|
}
|
||||||
|
if (entity == null) {
|
||||||
|
throw new HttpFailure(status, null);
|
||||||
|
}
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
return JsonSerialization.readValue(is, AccessTokenResponse.class);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static String readString(InputStream in) throws IOException
|
||||||
|
{
|
||||||
|
char[] buffer = new char[1024];
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||||
|
int wasRead = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
wasRead = reader.read(buffer, 0, 1024);
|
||||||
|
if (wasRead > 0)
|
||||||
|
{
|
||||||
|
builder.append(buffer, 0, wasRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (wasRead > -1);
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
|
||||||
|
String body = null;
|
||||||
|
if (entity != null) {
|
||||||
|
InputStream is = entity.getContent();
|
||||||
|
try {
|
||||||
|
body = readString(is);
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new HttpFailure(status, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String stripOauthParametersFromRedirect(String uri) {
|
||||||
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
|
||||||
|
.replaceQueryParam("code", null)
|
||||||
|
.replaceQueryParam("state", null);
|
||||||
|
return builder.build().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ public class AdapterConfig
|
||||||
protected String resource;
|
protected String resource;
|
||||||
@JsonProperty("realm-public-key")
|
@JsonProperty("realm-public-key")
|
||||||
protected String realmKey;
|
protected String realmKey;
|
||||||
@JsonProperty("auth-url")
|
@JsonProperty("auth-url")
|
||||||
protected String authUrl;
|
protected String authUrl;
|
||||||
@JsonProperty("code-url")
|
@JsonProperty("code-url")
|
||||||
protected String codeUrl;
|
protected String codeUrl;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package org.keycloak.adapters;
|
package org.keycloak.adapters.config;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
|
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import javax.ws.rs.core.Form;
|
import java.util.HashMap;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -13,10 +13,10 @@ import javax.ws.rs.core.UriBuilder;
|
||||||
*/
|
*/
|
||||||
public class RealmConfiguration {
|
public class RealmConfiguration {
|
||||||
protected ResourceMetadata metadata;
|
protected ResourceMetadata metadata;
|
||||||
protected ResteasyClient client;
|
protected HttpClient client;
|
||||||
protected UriBuilder authUrl;
|
protected KeycloakUriBuilder authUrl;
|
||||||
protected ResteasyWebTarget codeUrl;
|
protected String codeUrl;
|
||||||
protected Form resourceCredentials = new Form();
|
protected Map<String, String> resourceCredentials = new HashMap<String, String>();
|
||||||
protected boolean sslRequired = true;
|
protected boolean sslRequired = true;
|
||||||
protected String stateCookieName = "OAuth_Token_Request_State";
|
protected String stateCookieName = "OAuth_Token_Request_State";
|
||||||
|
|
||||||
|
@ -31,33 +31,6 @@ public class RealmConfiguration {
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResteasyClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClient(ResteasyClient client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UriBuilder getAuthUrl() {
|
|
||||||
return authUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAuthUrl(UriBuilder authUrl) {
|
|
||||||
this.authUrl = authUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Form getResourceCredentials() {
|
|
||||||
return resourceCredentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResteasyWebTarget getCodeUrl() {
|
|
||||||
return codeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCodeUrl(ResteasyWebTarget codeUrl) {
|
|
||||||
this.codeUrl = codeUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSslRequired() {
|
public boolean isSslRequired() {
|
||||||
return sslRequired;
|
return sslRequired;
|
||||||
|
@ -74,4 +47,37 @@ public class RealmConfiguration {
|
||||||
public void setStateCookieName(String stateCookieName) {
|
public void setStateCookieName(String stateCookieName) {
|
||||||
this.stateCookieName = stateCookieName;
|
this.stateCookieName = stateCookieName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HttpClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(HttpClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycloakUriBuilder getAuthUrl() {
|
||||||
|
return authUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthUrl(KeycloakUriBuilder authUrl) {
|
||||||
|
this.authUrl = authUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCodeUrl() {
|
||||||
|
return codeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCodeUrl(String codeUrl) {
|
||||||
|
this.codeUrl = codeUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getResourceCredentials() {
|
||||||
|
return resourceCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResourceCredentials(Map<String, String> resourceCredentials) {
|
||||||
|
this.resourceCredentials = resourceCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,22 +1,17 @@
|
||||||
package org.keycloak.adapters.undertow;
|
package org.keycloak.adapters.config;
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
|
||||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class RealmConfigurationLoader extends AdapterConfigLoader {
|
public class RealmConfigurationLoader extends AdapterConfigLoader {
|
||||||
protected ResteasyClient client;
|
protected HttpClient client;
|
||||||
protected RealmConfiguration realmConfiguration;
|
protected RealmConfiguration realmConfiguration;
|
||||||
|
|
||||||
public RealmConfigurationLoader() {
|
public RealmConfigurationLoader() {
|
||||||
|
@ -45,36 +40,23 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
|
||||||
}
|
}
|
||||||
realmConfiguration.setMetadata(resourceMetadata);
|
realmConfiguration.setMetadata(resourceMetadata);
|
||||||
realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
|
realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
|
||||||
|
realmConfiguration.setResourceCredentials(adapterConfig.getCredentials());
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : getAdapterConfig().getCredentials().entrySet()) {
|
HttpClient client = getClient();
|
||||||
realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResteasyClient client = getClient();
|
|
||||||
|
|
||||||
realmConfiguration.setClient(client);
|
realmConfiguration.setClient(client);
|
||||||
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
realmConfiguration.setAuthUrl(KeycloakUriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
||||||
realmConfiguration.setCodeUrl(client.target(tokenUrl));
|
realmConfiguration.setCodeUrl(tokenUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void initClient() {
|
protected void initClient() {
|
||||||
int size = 10;
|
int size = 10;
|
||||||
if (adapterConfig.getConnectionPoolSize() > 0)
|
if (adapterConfig.getConnectionPoolSize() > 0)
|
||||||
size = adapterConfig.getConnectionPoolSize();
|
size = adapterConfig.getConnectionPoolSize();
|
||||||
ResteasyClientBuilder.HostnameVerificationPolicy policy = ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
||||||
if (adapterConfig.isAllowAnyHostname())
|
if (adapterConfig.isAllowAnyHostname())
|
||||||
policy = ResteasyClientBuilder.HostnameVerificationPolicy.ANY;
|
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
|
||||||
ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
|
HttpClientBuilder builder = new HttpClientBuilder()
|
||||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
|
||||||
Thread.currentThread().setContextClassLoader(RealmConfigurationLoader.class.getClassLoader());
|
|
||||||
try {
|
|
||||||
ResteasyProviderFactory.getInstance(); // initialize builtins
|
|
||||||
RegisterBuiltin.register(providerFactory);
|
|
||||||
} finally {
|
|
||||||
Thread.currentThread().setContextClassLoader(old);
|
|
||||||
}
|
|
||||||
ResteasyClientBuilder builder = new ResteasyClientBuilder()
|
|
||||||
.providerFactory(providerFactory)
|
|
||||||
.connectionPoolSize(size)
|
.connectionPoolSize(size)
|
||||||
.hostnameVerification(policy)
|
.hostnameVerification(policy)
|
||||||
.keyStore(clientCertKeystore, adapterConfig.getClientKeyPassword());
|
.keyStore(clientCertKeystore, adapterConfig.getClientKeyPassword());
|
||||||
|
@ -86,7 +68,7 @@ public class RealmConfigurationLoader extends AdapterConfigLoader {
|
||||||
client = builder.build();
|
client = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResteasyClient getClient() {
|
public HttpClient getClient() {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,30 +23,38 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk16</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-xc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.web</groupId>
|
<groupId>org.jboss.web</groupId>
|
||||||
<artifactId>jbossweb</artifactId>
|
<artifactId>jbossweb</artifactId>
|
||||||
|
@ -59,12 +67,6 @@
|
||||||
<version>7.1.2.Final</version>
|
<version>7.1.2.Final</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.picketbox</groupId>
|
|
||||||
<artifactId>picketbox</artifactId>
|
|
||||||
<version>4.0.7.Final</version>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
|
|
@ -18,9 +18,9 @@ import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre-installed actions that must be authenticated
|
* Pre-installed actions that must be authenticated
|
||||||
*
|
* <p/>
|
||||||
* Actions include:
|
* Actions include:
|
||||||
*
|
* <p/>
|
||||||
* CORS Origin Check and Response headers
|
* CORS Origin Check and Response headers
|
||||||
* K_QUERY_BEARER_TOKEN: Get bearer token from server for Javascripts CORS requests
|
* K_QUERY_BEARER_TOKEN: Get bearer token from server for Javascripts CORS requests
|
||||||
*
|
*
|
||||||
|
@ -47,24 +47,24 @@ public class AuthenticatedActionsValve extends ValveBase {
|
||||||
if (corsRequest(request, response, session)) return;
|
if (corsRequest(request, response, session)) return;
|
||||||
String requestUri = request.getRequestURI();
|
String requestUri = request.getRequestURI();
|
||||||
if (requestUri.endsWith("K_QUERY_BEARER_TOKEN")) {
|
if (requestUri.endsWith("K_QUERY_BEARER_TOKEN")) {
|
||||||
queryBearerToken(request, response, session);
|
queryBearerToken(request, response, session);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getNext().invoke(request, response);
|
getNext().invoke(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SkeletonKeySession getSkeletonKeySession(Request request) {
|
public SkeletonKeySession getSkeletonKeySession(Request request) {
|
||||||
SkeletonKeySession skSession = (SkeletonKeySession)request.getAttribute(SkeletonKeySession.class.getName());
|
SkeletonKeySession skSession = (SkeletonKeySession) request.getAttribute(SkeletonKeySession.class.getName());
|
||||||
if (skSession != null) return skSession;
|
if (skSession != null) return skSession;
|
||||||
Session session = request.getSessionInternal();
|
Session session = request.getSessionInternal();
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
return (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
return (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
|
protected void queryBearerToken(Request request, Response response, SkeletonKeySession session) throws IOException, ServletException {
|
||||||
log.debugv("queryBearerToken {0}",request.getRequestURI());
|
log.debugv("queryBearerToken {0}", request.getRequestURI());
|
||||||
if (abortTokenResponse(request, response, session)) return;
|
if (abortTokenResponse(request, response, session)) return;
|
||||||
response.setStatus(200);
|
response.setStatus(200);
|
||||||
response.setContentType("text/plain");
|
response.setContentType("text/plain");
|
||||||
|
@ -75,7 +75,7 @@ public class AuthenticatedActionsValve extends ValveBase {
|
||||||
|
|
||||||
protected boolean abortTokenResponse(Request request, Response response, SkeletonKeySession session) throws IOException {
|
protected boolean abortTokenResponse(Request request, Response response, SkeletonKeySession session) throws IOException {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
log.debugv("session was null, sending back 401: {0}",request.getRequestURI());
|
log.debugv("session was null, sending back 401: {0}", request.getRequestURI());
|
||||||
response.sendError(401);
|
response.sendError(401);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ import org.apache.catalina.connector.Response;
|
||||||
import org.apache.catalina.core.StandardContext;
|
import org.apache.catalina.core.StandardContext;
|
||||||
import org.apache.catalina.deploy.LoginConfig;
|
import org.apache.catalina.deploy.LoginConfig;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
|
import org.keycloak.SkeletonKeySession;
|
||||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||||
import org.keycloak.adapters.config.AdapterConfig;
|
import org.keycloak.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
import org.keycloak.adapters.config.AdapterConfigLoader;
|
||||||
|
@ -63,7 +63,7 @@ public class BearerTokenAuthenticatorValve extends AuthenticatorBase implements
|
||||||
}
|
}
|
||||||
super.invoke(request, response);
|
super.invoke(request, response);
|
||||||
} finally {
|
} finally {
|
||||||
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
|
SkeletonKeySession.clearContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.keycloak.adapters.as7;
|
||||||
|
|
||||||
import org.apache.catalina.connector.Request;
|
import org.apache.catalina.connector.Request;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.SkeletonKeyPrincipal;
|
import org.keycloak.SkeletonKeyPrincipal;
|
||||||
|
@ -109,7 +108,7 @@ public class CatalinaBearerTokenAuthenticator {
|
||||||
request.setAuthType("OAUTH_BEARER");
|
request.setAuthType("OAUTH_BEARER");
|
||||||
SkeletonKeySession skSession = new SkeletonKeySession(tokenString, token, resourceMetadata);
|
SkeletonKeySession skSession = new SkeletonKeySession(tokenString, token, resourceMetadata);
|
||||||
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
||||||
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
|
SkeletonKeySession.pushContext(skSession);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,14 +13,13 @@ import org.apache.catalina.core.StandardContext;
|
||||||
import org.apache.catalina.deploy.LoginConfig;
|
import org.apache.catalina.deploy.LoginConfig;
|
||||||
import org.apache.catalina.realm.GenericPrincipal;
|
import org.apache.catalina.realm.GenericPrincipal;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.SkeletonKeyPrincipal;
|
import org.keycloak.SkeletonKeyPrincipal;
|
||||||
import org.keycloak.SkeletonKeySession;
|
import org.keycloak.SkeletonKeySession;
|
||||||
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
import org.keycloak.adapters.as7.config.CatalinaAdapterConfigLoader;
|
||||||
import org.keycloak.adapters.as7.config.RealmConfigurationLoader;
|
|
||||||
import org.keycloak.adapters.config.AdapterConfig;
|
import org.keycloak.adapters.config.AdapterConfig;
|
||||||
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
|
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
|
@ -43,9 +42,9 @@ import java.util.Set;
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class OAuthManagedResourceValve extends FormAuthenticator implements LifecycleListener {
|
public class OAuthAuthenticatorValve extends FormAuthenticator implements LifecycleListener {
|
||||||
protected RealmConfiguration realmConfiguration;
|
protected RealmConfiguration realmConfiguration;
|
||||||
private static final Logger log = Logger.getLogger(OAuthManagedResourceValve.class);
|
private static final Logger log = Logger.getLogger(OAuthAuthenticatorValve.class);
|
||||||
protected UserSessionManagement userSessionManagement = new UserSessionManagement();
|
protected UserSessionManagement userSessionManagement = new UserSessionManagement();
|
||||||
protected AdapterConfig adapterConfig;
|
protected AdapterConfig adapterConfig;
|
||||||
protected ResourceMetadata resourceMetadata;
|
protected ResourceMetadata resourceMetadata;
|
||||||
|
@ -87,7 +86,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
||||||
}
|
}
|
||||||
super.invoke(request, response);
|
super.invoke(request, response);
|
||||||
} finally {
|
} finally {
|
||||||
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
|
SkeletonKeySession.clearContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +164,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
||||||
return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
String user = action.getUser();
|
String user = action.getUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
log.debug("logout of session for: " + user);
|
log.debug("logout of session for: " + user);
|
||||||
userSessionManagement.logout(user);
|
userSessionManagement.logout(user);
|
||||||
|
@ -200,7 +199,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
|
||||||
SkeletonKeySession skSession = (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
SkeletonKeySession skSession = (SkeletonKeySession) session.getNote(SkeletonKeySession.class.getName());
|
||||||
if (skSession != null) {
|
if (skSession != null) {
|
||||||
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
|
||||||
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
|
SkeletonKeySession.pushContext(skSession);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,19 +2,16 @@ package org.keycloak.adapters.as7;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.adapters.TokenGrantRequest;
|
||||||
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.core.Form;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
@ -137,7 +134,7 @@ public class ServletOAuthLogin {
|
||||||
// disabled?
|
// disabled?
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1);
|
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
|
||||||
if (port != 443) secureUrl.port(port);
|
if (port != 443) secureUrl.port(port);
|
||||||
url = secureUrl.build().toString();
|
url = secureUrl.build().toString();
|
||||||
}
|
}
|
||||||
|
@ -159,7 +156,7 @@ public class ServletOAuthLogin {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
String redirect = getRedirectUri(state);
|
String redirect = getRedirectUri(state);
|
||||||
if (redirect == null) {
|
if (redirect == null) {
|
||||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCookie(realmInfo.getStateCookieName(), state, null, getDefaultCookiePath(), realmInfo.isSslRequired());
|
setCookie(realmInfo.getStateCookieName(), state, null, getDefaultCookiePath(), realmInfo.isSslRequired());
|
||||||
|
@ -216,42 +213,28 @@ public class ServletOAuthLogin {
|
||||||
// abort if not HTTPS
|
// abort if not HTTPS
|
||||||
if (realmInfo.isSslRequired() && !isRequestSecure()) {
|
if (realmInfo.isSslRequired() && !isRequestSecure()) {
|
||||||
log.error("SSL is required");
|
log.error("SSL is required");
|
||||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkStateCookie()) return false;
|
if (!checkStateCookie()) return false;
|
||||||
|
|
||||||
String client_id = realmInfo.getMetadata().getResourceName();
|
|
||||||
String password = realmInfo.getResourceCredentials().asMap().getFirst("password");
|
|
||||||
//String authHeader = BasicAuthHelper.createHeader(client_id, password);
|
|
||||||
String redirectUri = stripOauthParametersFromRedirect();
|
String redirectUri = stripOauthParametersFromRedirect();
|
||||||
Form form = new Form();
|
AccessTokenResponse tokenResponse = null;
|
||||||
form.param("grant_type", "authorization_code")
|
|
||||||
.param("code", code)
|
|
||||||
.param("client_id", client_id)
|
|
||||||
.param(CredentialRepresentation.PASSWORD, password)
|
|
||||||
.param("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
Response res = realmInfo.getCodeUrl().request()
|
|
||||||
//.header(HttpHeaders.AUTHORIZATION, authHeader)
|
|
||||||
.post(Entity.form(form));
|
|
||||||
AccessTokenResponse tokenResponse;
|
|
||||||
try {
|
try {
|
||||||
if (res.getStatus() != 200) {
|
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
|
||||||
log.error("failed to turn code into token");
|
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||||
log.error("status from server: " + res.getStatus());
|
log.error("failed to turn code into token");
|
||||||
if (res.getStatus() == 400 && res.getMediaType() != null) {
|
log.error("status from server: " + failure.getStatus());
|
||||||
log.error(" " + res.readEntity(String.class));
|
if (failure.getStatus() == 400 && failure.getError() != null) {
|
||||||
}
|
log.error(" " + failure.getError());
|
||||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
log.debug("media type: " + res.getMediaType());
|
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
|
return false;
|
||||||
tokenResponse = res.readEntity(AccessTokenResponse.class);
|
|
||||||
} finally {
|
} catch (IOException e) {
|
||||||
res.close();
|
log.error("failed to turn code into token");
|
||||||
|
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenString = tokenResponse.getToken();
|
tokenString = tokenResponse.getToken();
|
||||||
|
@ -260,7 +243,7 @@ public class ServletOAuthLogin {
|
||||||
log.debug("Token Verification succeeded!");
|
log.debug("Token Verification succeeded!");
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
log.error("failed verification of token");
|
log.error("failed verification of token");
|
||||||
sendError(Response.Status.FORBIDDEN.getStatusCode());
|
sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// redirect to URL without oauth query parameters
|
// redirect to URL without oauth query parameters
|
||||||
|
@ -273,7 +256,7 @@ public class ServletOAuthLogin {
|
||||||
*/
|
*/
|
||||||
protected String stripOauthParametersFromRedirect() {
|
protected String stripOauthParametersFromRedirect() {
|
||||||
StringBuffer buf = request.getRequestURL().append("?").append(request.getQueryString());
|
StringBuffer buf = request.getRequestURL().append("?").append(request.getQueryString());
|
||||||
UriBuilder builder = UriBuilder.fromUri(buf.toString())
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(buf.toString())
|
||||||
.replaceQueryParam("code", null)
|
.replaceQueryParam("code", null)
|
||||||
.replaceQueryParam("state", null);
|
.replaceQueryParam("state", null);
|
||||||
return builder.build().toString();
|
return builder.build().toString();
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package org.keycloak.adapters.as7.config;
|
package org.keycloak.adapters.as7.config;
|
||||||
|
|
||||||
import org.apache.catalina.Context;
|
import org.apache.catalina.Context;
|
||||||
|
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class CatalinaAdapterConfigLoader extends RealmConfigurationLoader
|
public class CatalinaAdapterConfigLoader extends RealmConfigurationLoader {
|
||||||
{
|
|
||||||
|
|
||||||
public CatalinaAdapterConfigLoader(Context context) {
|
public CatalinaAdapterConfigLoader(Context context) {
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
package org.keycloak.adapters.as7.config;
|
|
||||||
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
|
||||||
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
|
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
|
||||||
import org.keycloak.adapters.config.AdapterConfigLoader;
|
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class RealmConfigurationLoader extends AdapterConfigLoader {
|
|
||||||
protected ResteasyClient client;
|
|
||||||
protected RealmConfiguration realmConfiguration;
|
|
||||||
|
|
||||||
public RealmConfigurationLoader() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public RealmConfigurationLoader(InputStream is) {
|
|
||||||
loadConfig(is);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(boolean setupClient) {
|
|
||||||
init();
|
|
||||||
initRealmConfiguration(setupClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initRealmConfiguration(boolean setupClient) {
|
|
||||||
if (!setupClient || adapterConfig.isBearerOnly()) return;
|
|
||||||
initClient();
|
|
||||||
realmConfiguration = new RealmConfiguration();
|
|
||||||
String authUrl = adapterConfig.getAuthUrl();
|
|
||||||
if (authUrl == null) {
|
|
||||||
throw new RuntimeException("You must specify auth-url");
|
|
||||||
}
|
|
||||||
String tokenUrl = adapterConfig.getCodeUrl();
|
|
||||||
if (tokenUrl == null) {
|
|
||||||
throw new RuntimeException("You mut specify code-url");
|
|
||||||
}
|
|
||||||
realmConfiguration.setMetadata(resourceMetadata);
|
|
||||||
realmConfiguration.setSslRequired(!adapterConfig.isSslNotRequired());
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : getAdapterConfig().getCredentials().entrySet()) {
|
|
||||||
realmConfiguration.getResourceCredentials().param(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResteasyClient client = getClient();
|
|
||||||
|
|
||||||
realmConfiguration.setClient(client);
|
|
||||||
realmConfiguration.setAuthUrl(UriBuilder.fromUri(authUrl).queryParam("client_id", resourceMetadata.getResourceName()));
|
|
||||||
realmConfiguration.setCodeUrl(client.target(tokenUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initClient() {
|
|
||||||
int size = 10;
|
|
||||||
if (adapterConfig.getConnectionPoolSize() > 0)
|
|
||||||
size = adapterConfig.getConnectionPoolSize();
|
|
||||||
ResteasyClientBuilder.HostnameVerificationPolicy policy = ResteasyClientBuilder.HostnameVerificationPolicy.WILDCARD;
|
|
||||||
if (adapterConfig.isAllowAnyHostname())
|
|
||||||
policy = ResteasyClientBuilder.HostnameVerificationPolicy.ANY;
|
|
||||||
ResteasyProviderFactory providerFactory = new ResteasyProviderFactory();
|
|
||||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
|
||||||
Thread.currentThread().setContextClassLoader(RealmConfigurationLoader.class.getClassLoader());
|
|
||||||
try {
|
|
||||||
ResteasyProviderFactory.getInstance(); // initialize builtins
|
|
||||||
RegisterBuiltin.register(providerFactory);
|
|
||||||
} finally {
|
|
||||||
Thread.currentThread().setContextClassLoader(old);
|
|
||||||
}
|
|
||||||
ResteasyClientBuilder builder = new ResteasyClientBuilder()
|
|
||||||
.providerFactory(providerFactory)
|
|
||||||
.connectionPoolSize(size)
|
|
||||||
.hostnameVerification(policy)
|
|
||||||
.keyStore(clientCertKeystore, adapterConfig.getClientKeyPassword());
|
|
||||||
if (adapterConfig.isDisableTrustManager()) {
|
|
||||||
builder.disableTrustManager();
|
|
||||||
} else {
|
|
||||||
builder.trustStore(truststore);
|
|
||||||
}
|
|
||||||
client = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResteasyClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RealmConfiguration getRealmConfiguration() {
|
|
||||||
return realmConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
71
integration/jaxrs-oauth-client/pom.xml
Executable file
71
integration/jaxrs-oauth-client/pom.xml
Executable file
|
@ -0,0 +1,71 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project>
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-alpha-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
||||||
|
<name>Keycloak JAX-RS OAuth Client</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-client</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jackson-provider</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -1,11 +1,17 @@
|
||||||
package org.keycloak.jaxrs;
|
package org.keycloak.jaxrs;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
import org.jboss.resteasy.logging.Logger;
|
import org.jboss.resteasy.logging.Logger;
|
||||||
|
import org.jboss.resteasy.util.BasicAuthHelper;
|
||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
import javax.ws.rs.InternalServerErrorException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
|
import javax.ws.rs.core.Form;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.NewCookie;
|
import javax.ws.rs.core.NewCookie;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -21,6 +27,56 @@ import java.net.URI;
|
||||||
*/
|
*/
|
||||||
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
public class JaxrsOAuthClient extends AbstractOAuthClient {
|
||||||
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
protected static final Logger logger = Logger.getLogger(JaxrsOAuthClient.class);
|
||||||
|
protected Client client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Client for obtaining access token from code
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (client == null) {
|
||||||
|
client = new ResteasyClientBuilder().trustStore(truststore)
|
||||||
|
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
|
||||||
|
.connectionPoolSize(10)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* closes cllient
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
public Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(Client client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveBearerToken(String redirectUri, String code) {
|
||||||
|
redirectUri = stripOauthParametersFromRedirect(redirectUri);
|
||||||
|
String authHeader = BasicAuthHelper.createHeader(clientId, password);
|
||||||
|
Form codeForm = new Form()
|
||||||
|
.param("grant_type", "authorization_code")
|
||||||
|
.param("code", code)
|
||||||
|
.param("client_id", clientId)
|
||||||
|
.param("password", password)
|
||||||
|
.param("redirect_uri", redirectUri);
|
||||||
|
Response res = client.target(codeUrl).request().header(HttpHeaders.AUTHORIZATION, authHeader).post(Entity.form(codeForm));
|
||||||
|
try {
|
||||||
|
if (res.getStatus() == 400) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
} else if (res.getStatus() != 200) {
|
||||||
|
throw new InternalServerErrorException(new Exception("Unknown error when getting acess token"));
|
||||||
|
}
|
||||||
|
AccessTokenResponse tokenResponse = res.readEntity(AccessTokenResponse.class);
|
||||||
|
return tokenResponse.getToken();
|
||||||
|
} finally {
|
||||||
|
res.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
public Response redirect(UriInfo uriInfo, String redirectUri) {
|
||||||
return redirect(uriInfo, redirectUri, null);
|
return redirect(uriInfo, redirectUri, null);
|
||||||
}
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>adapter-core</module>
|
<module>adapter-core</module>
|
||||||
|
<module>jaxrs-oauth-client</module>
|
||||||
|
<module>servlet-oauth-client</module>
|
||||||
<module>as7-eap6/adapter</module>
|
<module>as7-eap6/adapter</module>
|
||||||
<module>undertow</module>
|
<module>undertow</module>
|
||||||
<!-- <module>as7-eap6/jboss-modules</module> -->
|
<!-- <module>as7-eap6/jboss-modules</module> -->
|
||||||
|
|
67
integration/servlet-oauth-client/pom.xml
Executable file
67
integration/servlet-oauth-client/pom.xml
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project>
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-alpha-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-servlet-oauth-client</artifactId>
|
||||||
|
<name>Keycloak Servlet OAuth Client</name>
|
||||||
|
<description/>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk16</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -1,23 +1,58 @@
|
||||||
package org.keycloak.servlet;
|
package org.keycloak.servlet;
|
||||||
|
|
||||||
import org.jboss.resteasy.plugins.server.servlet.ServletUtil;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.jboss.resteasy.spi.ResteasyUriInfo;
|
|
||||||
import org.keycloak.AbstractOAuthClient;
|
import org.keycloak.AbstractOAuthClient;
|
||||||
|
import org.keycloak.adapters.HttpClientBuilder;
|
||||||
|
import org.keycloak.adapters.TokenGrantRequest;
|
||||||
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.ws.rs.BadRequestException;
|
|
||||||
import javax.ws.rs.InternalServerErrorException;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class ServletOAuthClient extends AbstractOAuthClient {
|
public class ServletOAuthClient extends AbstractOAuthClient {
|
||||||
|
protected HttpClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Client for obtaining access token from code
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
if (client == null) {
|
||||||
|
client = new HttpClientBuilder().trustStore(truststore)
|
||||||
|
.hostnameVerification(HttpClientBuilder.HostnameVerificationPolicy.ANY)
|
||||||
|
.connectionPoolSize(10)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* closes cllient
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
client.getConnectionManager().shutdown();
|
||||||
|
}
|
||||||
|
public HttpClient getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setClient(HttpClient client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveBearerToken(String redirectUri, String code) throws IOException, TokenGrantRequest.HttpFailure {
|
||||||
|
Map<String, String> credentials = new HashMap<String, String>();
|
||||||
|
credentials.put("password", password);
|
||||||
|
return TokenGrantRequest.invoke(client, code, codeUrl, redirectUri, clientId, credentials).getToken();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the process of obtaining an access token by redirecting the browser
|
* Start the process of obtaining an access token by redirecting the browser
|
||||||
* to the authentication server
|
* to the authentication server
|
||||||
|
@ -28,8 +63,11 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
public void redirectRelative(String relativePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
public void redirectRelative(String relativePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(request.getRequestURL().toString())
|
||||||
String redirect = uriInfo.getBaseUriBuilder().path(relativePath).toTemplate();
|
.replacePath(request.getContextPath())
|
||||||
|
.replaceQuery(null)
|
||||||
|
.path(relativePath);
|
||||||
|
String redirect = builder.toTemplate();
|
||||||
redirect(redirect, request, response);
|
redirect(redirect, request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +84,7 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
||||||
public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||||
String state = getStateCode();
|
String state = getStateCode();
|
||||||
|
|
||||||
URI url = UriBuilder.fromUri(authUrl)
|
URI url = KeycloakUriBuilder.fromUri(authUrl)
|
||||||
.queryParam("client_id", clientId)
|
.queryParam("client_id", clientId)
|
||||||
.queryParam("redirect_uri", redirectUri)
|
.queryParam("redirect_uri", redirectUri)
|
||||||
.queryParam("state", state)
|
.queryParam("state", state)
|
||||||
|
@ -92,24 +130,24 @@ public class ServletOAuthClient extends AbstractOAuthClient {
|
||||||
*
|
*
|
||||||
* @param request
|
* @param request
|
||||||
* @return
|
* @return
|
||||||
* @throws BadRequestException
|
* @throws IOException
|
||||||
* @throws InternalServerErrorException
|
* @throws org.keycloak.adapters.TokenGrantRequest.HttpFailure
|
||||||
*/
|
*/
|
||||||
public String getBearerToken(HttpServletRequest request) throws BadRequestException, InternalServerErrorException {
|
public String getBearerToken(HttpServletRequest request) throws IOException, TokenGrantRequest.HttpFailure {
|
||||||
String error = request.getParameter("error");
|
String error = request.getParameter("error");
|
||||||
if (error != null) throw new BadRequestException(new Exception("OAuth error: " + error));
|
if (error != null) throw new IOException("OAuth error: " + error);
|
||||||
String redirectUri = request.getRequestURL().append("?").append(request.getQueryString()).toString();
|
String redirectUri = request.getRequestURL().append("?").append(request.getQueryString()).toString();
|
||||||
String stateCookie = getCookieValue(stateCookieName, request);
|
String stateCookie = getCookieValue(stateCookieName, request);
|
||||||
if (stateCookie == null) throw new BadRequestException(new Exception("state cookie not set"));
|
if (stateCookie == null) throw new IOException("state cookie not set");
|
||||||
// we can call get parameter as this should be a redirect
|
// we can call get parameter as this should be a redirect
|
||||||
String state = request.getParameter("state");
|
String state = request.getParameter("state");
|
||||||
String code = request.getParameter("code");
|
String code = request.getParameter("code");
|
||||||
|
|
||||||
if (state == null) throw new BadRequestException(new Exception("state parameter was null"));
|
if (state == null) throw new IOException("state parameter was null");
|
||||||
if (!state.equals(stateCookie)) {
|
if (!state.equals(stateCookie)) {
|
||||||
throw new BadRequestException(new Exception("state parameter invalid"));
|
throw new IOException("state parameter invalid");
|
||||||
}
|
}
|
||||||
if (code == null) throw new BadRequestException(new Exception("code parameter was null"));
|
if (code == null) throw new IOException("code parameter was null");
|
||||||
return resolveBearerToken(redirectUri, code);
|
return resolveBearerToken(redirectUri, code);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,29 +23,38 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-core</artifactId>
|
<artifactId>keycloak-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-adapter-core</artifactId>
|
<artifactId>keycloak-adapter-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk16</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-core-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-xc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.spec.javax.servlet</groupId>
|
<groupId>org.jboss.spec.javax.servlet</groupId>
|
||||||
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-jaxrs</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
|
||||||
<artifactId>resteasy-client</artifactId>
|
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.undertow</groupId>
|
<groupId>io.undertow</groupId>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.undertow.security.idm.Account;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.AttachmentKey;
|
import io.undertow.util.AttachmentKey;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.SkeletonKeyPrincipal;
|
import org.keycloak.SkeletonKeyPrincipal;
|
||||||
import org.keycloak.SkeletonKeySession;
|
import org.keycloak.SkeletonKeySession;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import io.undertow.servlet.api.DeploymentInfo;
|
||||||
import io.undertow.servlet.api.ServletSessionConfig;
|
import io.undertow.servlet.api.ServletSessionConfig;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.adapters.config.AdapterConfig;
|
import org.keycloak.adapters.config.AdapterConfig;
|
||||||
|
import org.keycloak.adapters.config.RealmConfigurationLoader;
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
|
@ -8,16 +8,14 @@ import io.undertow.server.handlers.CookieImpl;
|
||||||
import io.undertow.util.Headers;
|
import io.undertow.util.Headers;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
|
import org.keycloak.adapters.TokenGrantRequest;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.SkeletonKeyToken;
|
import org.keycloak.representations.SkeletonKeyToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.util.KeycloakUriBuilder;
|
||||||
|
|
||||||
import javax.ws.rs.client.Entity;
|
import java.io.IOException;
|
||||||
import javax.ws.rs.core.Form;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -60,7 +58,7 @@ public class OAuthAuthenticator {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getRequestUrl() {
|
protected String getRequestUrl() {
|
||||||
UriBuilder uriBuilder = UriBuilder.fromUri(exchange.getRequestURI())
|
KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
|
||||||
.replaceQuery(exchange.getQueryString());
|
.replaceQuery(exchange.getQueryString());
|
||||||
if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
|
if (!exchange.isHostIncludedInRequestURI()) uriBuilder.scheme(exchange.getRequestScheme()).host(exchange.getHostAndPort());
|
||||||
return uriBuilder.build().toString();
|
return uriBuilder.build().toString();
|
||||||
|
@ -107,7 +105,7 @@ public class OAuthAuthenticator {
|
||||||
// disabled?
|
// disabled?
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
UriBuilder secureUrl = UriBuilder.fromUri(url).scheme("https").port(-1);
|
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
|
||||||
if (port != 443) secureUrl.port(port);
|
if (port != 443) secureUrl.port(port);
|
||||||
url = secureUrl.build().toString();
|
url = secureUrl.build().toString();
|
||||||
}
|
}
|
||||||
|
@ -237,34 +235,20 @@ public class OAuthAuthenticator {
|
||||||
KeycloakChallenge challenge = checkStateCookie();
|
KeycloakChallenge challenge = checkStateCookie();
|
||||||
if (challenge != null) return challenge;
|
if (challenge != null) return challenge;
|
||||||
|
|
||||||
String client_id = realmInfo.getMetadata().getResourceName();
|
AccessTokenResponse tokenResponse = null;
|
||||||
String password = realmInfo.getResourceCredentials().asMap().getFirst("password");
|
|
||||||
//String authHeader = BasicAuthHelper.createHeader(client_id, password);
|
|
||||||
redirectUri = stripOauthParametersFromRedirect();
|
|
||||||
Form form = new Form();
|
|
||||||
form.param("grant_type", "authorization_code")
|
|
||||||
.param("code", code)
|
|
||||||
.param("client_id", client_id)
|
|
||||||
.param(CredentialRepresentation.PASSWORD, password)
|
|
||||||
.param("redirect_uri", redirectUri);
|
|
||||||
|
|
||||||
Response res = realmInfo.getCodeUrl().request()
|
|
||||||
.post(Entity.form(form));
|
|
||||||
AccessTokenResponse tokenResponse;
|
|
||||||
try {
|
try {
|
||||||
if (res.getStatus() != 200) {
|
tokenResponse = TokenGrantRequest.invoke(realmInfo, code, redirectUri);
|
||||||
log.error("failed to turn code into token");
|
} catch (TokenGrantRequest.HttpFailure failure) {
|
||||||
log.error("status from server: " + res.getStatus());
|
log.error("failed to turn code into token");
|
||||||
if (res.getStatus() == 400 && res.getMediaType() != null) {
|
log.error("status from server: " + failure.getStatus());
|
||||||
log.error(" " + res.readEntity(String.class));
|
if (failure.getStatus() == 400 && failure.getError() != null) {
|
||||||
}
|
log.error(" " + failure.getError());
|
||||||
return challenge(403);
|
|
||||||
}
|
}
|
||||||
log.debug("media type: " + res.getMediaType());
|
return challenge(403);
|
||||||
log.debug("Content-Type header: " + res.getHeaderString("Content-Type"));
|
|
||||||
tokenResponse = res.readEntity(AccessTokenResponse.class);
|
} catch (IOException e) {
|
||||||
} finally {
|
log.error("failed to turn code into token");
|
||||||
res.close();
|
return challenge(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenString = tokenResponse.getToken();
|
tokenString = tokenResponse.getToken();
|
||||||
|
@ -283,7 +267,7 @@ public class OAuthAuthenticator {
|
||||||
* strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
|
* strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
|
||||||
*/
|
*/
|
||||||
protected String stripOauthParametersFromRedirect() {
|
protected String stripOauthParametersFromRedirect() {
|
||||||
UriBuilder builder = UriBuilder.fromUri(exchange.getRequestURI())
|
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(exchange.getRequestURI())
|
||||||
.replaceQuery(exchange.getQueryString())
|
.replaceQuery(exchange.getQueryString())
|
||||||
.replaceQueryParam("code", null)
|
.replaceQueryParam("code", null)
|
||||||
.replaceQueryParam("state", null);
|
.replaceQueryParam("state", null);
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.keycloak.adapters.undertow;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.servlet.api.ConfidentialPortManager;
|
import io.undertow.servlet.api.ConfidentialPortManager;
|
||||||
import io.undertow.servlet.handlers.ServletRequestContext;
|
import io.undertow.servlet.handlers.ServletRequestContext;
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
import org.keycloak.ResourceMetadata;
|
import org.keycloak.ResourceMetadata;
|
||||||
import org.keycloak.SkeletonKeySession;
|
import org.keycloak.SkeletonKeySession;
|
||||||
import org.keycloak.adapters.config.AdapterConfig;
|
import org.keycloak.adapters.config.AdapterConfig;
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.keycloak.adapters.undertow;
|
||||||
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.servlet.api.ConfidentialPortManager;
|
import io.undertow.servlet.api.ConfidentialPortManager;
|
||||||
import org.keycloak.adapters.RealmConfiguration;
|
import org.keycloak.adapters.config.RealmConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
|
11
pom.xml
11
pom.xml
|
@ -76,8 +76,8 @@
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>core-jaxrs</module>
|
<module>core-jaxrs</module>
|
||||||
<module>model</module>
|
<module>model</module>
|
||||||
<module>services</module>
|
|
||||||
<module>integration</module>
|
<module>integration</module>
|
||||||
|
<module>services</module>
|
||||||
<module>social</module>
|
<module>social</module>
|
||||||
<module>forms</module>
|
<module>forms</module>
|
||||||
<module>admin-ui-styles</module>
|
<module>admin-ui-styles</module>
|
||||||
|
@ -327,7 +327,14 @@
|
||||||
<artifactId>mysql-connector-java</artifactId>
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
<version>${mysql.version}</version>
|
<version>${mysql.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Keep this at 4.1.2 to make sure we're compatible with AS7 -->
|
||||||
|
<!-- the dependency seems to override Resteasy 3.0.5's dependeing on 4.2.1
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,12 @@
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-jpa</artifactId>
|
<artifactId>keycloak-model-jpa</artifactId>
|
||||||
|
@ -83,7 +89,7 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.resteasy</groupId>
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
<artifactId>jaxrs-api</artifactId>
|
<artifactId>jaxrs-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
|
@ -162,11 +168,11 @@
|
||||||
<artifactId>hibernate-entitymanager</artifactId>
|
<artifactId>hibernate-entitymanager</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.icegreen</groupId>
|
<groupId>com.icegreen</groupId>
|
||||||
<artifactId>greenmail</artifactId>
|
<artifactId>greenmail</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -34,7 +35,13 @@ public class DummySocialServlet extends HttpServlet {
|
||||||
String state = null;
|
String state = null;
|
||||||
String redirectUri = null;
|
String redirectUri = null;
|
||||||
|
|
||||||
List<NameValuePair> query = URLEncodedUtils.parse(req.getQueryString(), Charset.forName("UTF-8"));
|
List<NameValuePair> query = null;
|
||||||
|
try {
|
||||||
|
URI uri = URI.create(req.getRequestURL().append('?').append(req.getQueryString()).toString());
|
||||||
|
query = URLEncodedUtils.parse(uri, "UTF-8");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
for (NameValuePair p : query) {
|
for (NameValuePair p : query) {
|
||||||
if ("state".equals(p.getName())) {
|
if ("state".equals(p.getName())) {
|
||||||
state = p.getValue();
|
state = p.getValue();
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.openqa.selenium.By;
|
||||||
import org.openqa.selenium.WebDriver;
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -127,7 +128,12 @@ public class OAuthClient {
|
||||||
parameters.add(new BasicNameValuePair("password", password));
|
parameters.add(new BasicNameValuePair("password", password));
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charset.forName("UTF-8"));
|
UrlEncodedFormEntity formEntity = null;
|
||||||
|
try {
|
||||||
|
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
post.setEntity(formEntity);
|
post.setEntity(formEntity);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue