Compare commits

..
Author SHA1 Message Date
keycloak-bot 0a2da92cd1 Set version to 2.0.0.Test2 2016-07-06 12:48:30 +00:00
keycloak-bot 02eae5bcba Set version to 2.0.1.CR1-SNAPSHOT 2016-06-30 08:17:40 +00:00
keycloak-bot 3f67c59e21 Set version to 2.0.0.Final 2016-06-30 06:54:55 +00:00
Stian Thorgersen 9d8a1edbe9 KEYCLOAK-3172 Add view authorization role to existing realms 2016-06-30 08:51:49 +02:00
Pedro Igor b0ff50652c [KEYCLOAK-3172] - Migrating older versions with authorization services. 2016-06-30 07:34:49 +02:00
Stian Thorgersen 1255118b9e Merge pull request #2969 from Bouhnosaure/patch-1
Update messages_fr.properties
2016-06-30 07:28:39 +02:00
Marek Posolda 4f548b350a Merge pull request #2973 from mposolda/2.0.x
KEYCLOAK-3194 Fix fuse adapter. Remove karaf related steps from README
2016-06-27 22:17:35 +02:00
mposolda 81472362d3 KEYCLOAK-3194 Fix fuse adapter. Remove karaf related steps from README 2016-06-27 15:42:17 +02:00
Alexandre 43ad2e1c12 Update messages_fr.properties
Change the word "adminitrateur" to "administrateur"

The "s" was missing in "admini-S-trateur"

That all !
2016-06-26 07:49:40 +09:00
Pedro Igor df8d4cd35d Merge pull request #2968 from iperdomo/2.0.x
[KEYCLOAK-3185] Fix navigation resource > permission
2016-06-24 23:38:08 -03:00
Iván Perdomo ad88de1f79 [KEYCLOAK-3185] Fix navigation resource > permission 2016-06-24 18:02:18 +02:00
keycloak-bot 96311381ef Set version to 2.0.0.Final-SNAPSHOT 2016-06-23 15:19:47 +00:00
keycloak-bot d2986abd13 Set version to 2.0.0.CR1 2016-06-23 13:50:29 +00:00
5830 changed files with 183096 additions and 569556 deletions

No files matched your search

-4
View File
@@ -14,7 +14,6 @@
nbactions.xml nbactions.xml
nb-configuration.xml nb-configuration.xml
catalog.xml catalog.xml
nbproject
# Compiled source # # Compiled source #
################### ###################
@@ -46,6 +45,3 @@ nbproject
######### #########
target target
# Maven shade
#############
*dependency-reduced-pom.xml
+2 -15
View File
@@ -1,22 +1,9 @@
language: java language: java
cache:
directories:
- $HOME/.m2
before_cache:
- rm -rf $HOME/.m2/repository/org/keycloak
env: env:
global: global:
- MAVEN_SKIP_RC=true - MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m" - MAVEN_OPTS="-Xms512m -Xmx2048m"
matrix:
- TESTS=group1
- TESTS=group2
- TESTS=group3
- TESTS=group4
- TESTS=old
jdk: jdk:
- oraclejdk8 - oraclejdk8
@@ -25,9 +12,9 @@ before_script:
- export MAVEN_SKIP_RC=true - export MAVEN_SKIP_RC=true
install: install:
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTestsuite -B -V -q - mvn install -Pdistribution -DskipTests=true -B -V -q
script: script:
- ./travis-run-tests.sh $TESTS - mvn test -B
sudo: false sudo: false
+7 -8
View File
@@ -9,7 +9,7 @@ For more information about Keycloak visit [Keycloak homepage](http://keycloak.or
Building Building
-------- --------
Ensure you have JDK 8 (or newer), Maven 3.1.1 (or newer) and Git installed Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
java -version java -version
mvn -version mvn -version
@@ -36,15 +36,15 @@ Once completed you will find distribution archives in `distribution`.
Starting Keycloak Starting Keycloak
----------------- -----------------
To start Keycloak during development first build as specified above, then run: To start Keycloak during development first build as specficied above, then run:
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
To start Keycloak from the server distribution first build the distribution it as specified above, then run: To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
tar xfz distribution/server-dist/target/keycloak-<VERSION>.tar.gz tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
cd keycloak-<VERSION> cd keycloak-appliance-dist-all-<VERSION>/keycloak
bin/standalone.sh bin/standalone.sh
To stop the server press `Ctrl + C`. To stop the server press `Ctrl + C`.
@@ -52,7 +52,7 @@ To stop the server press `Ctrl + C`.
Help and Documentation Help and Documentation
---------------------- ----------------------
* [Documentation](http://www.keycloak.org/documentation.html) - User Guide, Admin REST API and Javadocs * [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak * [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests * [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
@@ -65,11 +65,10 @@ Contributing
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server) * [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases * [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database * [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
* [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak * [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
License License
------- -------
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) * [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -17,21 +17,26 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider; import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.rotation.PublicKeyLocator;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.PublishedRealmRepresentation;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.security.PublicKey;
import java.util.Map; import java.util.Map;
/** /**
@@ -52,7 +57,7 @@ public class AdapterDeploymentContext {
* during the application deployment's life cycle. * during the application deployment's life cycle.
* *
* @param deployment A KeycloakConfigResolver, possibly missing the Auth * @param deployment A KeycloakConfigResolver, possibly missing the Auth
* Server URL * Server URL and/or Realm Public Key
*/ */
public AdapterDeploymentContext(KeycloakDeployment deployment) { public AdapterDeploymentContext(KeycloakDeployment deployment) {
this.deployment = deployment; this.deployment = deployment;
@@ -74,6 +79,7 @@ public class AdapterDeploymentContext {
/** /**
* For single-tenant deployments, it complements KeycloakDeployment * For single-tenant deployments, it complements KeycloakDeployment
* by resolving a relative Auth Server's URL based on the current request * by resolving a relative Auth Server's URL based on the current request
* and, if needed, will lazily resolve the Realm's Public Key.
* *
* For multi-tenant deployments, defers the resolution of KeycloakDeployment * For multi-tenant deployments, defers the resolution of KeycloakDeployment
* to the KeycloakConfigResolver . * to the KeycloakConfigResolver .
@@ -92,8 +98,8 @@ public class AdapterDeploymentContext {
if (deployment.getAuthServerBaseUrl() == null) return deployment; if (deployment.getAuthServerBaseUrl() == null) return deployment;
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade); KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
if (resolvedDeployment.getPublicKeyLocator() == null) { if (resolvedDeployment.getRealmKey() == null) {
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs"); resolveRealmKey(resolvedDeployment);
} }
return resolvedDeployment; return resolvedDeployment;
} }
@@ -109,6 +115,45 @@ public class AdapterDeploymentContext {
} }
} }
public void resolveRealmKey(KeycloakDeployment deployment) {
if (deployment.getClient() == null) {
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
}
HttpGet get = new HttpGet(deployment.getRealmInfoUrl());
try {
HttpResponse response = deployment.getClient().execute(get);
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
close(response);
throw new RuntimeException("Unable to resolve realm public key remotely, status = " + status);
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new RuntimeException("Unable to resolve realm public key remotely. There was no entity.");
}
InputStream is = entity.getContent();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
PublishedRealmRepresentation rep = JsonSerialization.readValue(json, PublishedRealmRepresentation.class);
deployment.setRealmKey(rep.getPublicKey());
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
} catch (IOException e) {
throw new RuntimeException("Unable to resolve realm public key remotely", e);
}
}
/** /**
* This delegate is used to store temporary, per-request metadata like request resolved URLs. * This delegate is used to store temporary, per-request metadata like request resolved URLs.
* Ever method is delegated except URL get methods and isConfigured() * Ever method is delegated except URL get methods and isConfigured()
@@ -162,11 +207,6 @@ public class AdapterDeploymentContext {
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl(); return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
} }
@Override
public String getJwksUrl() {
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
}
@Override @Override
public String getResourceName() { public String getResourceName() {
return delegate.getResourceName(); return delegate.getResourceName();
@@ -183,13 +223,13 @@ public class AdapterDeploymentContext {
} }
@Override @Override
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) { public PublicKey getRealmKey() {
delegate.setPublicKeyLocator(publicKeyLocator); return delegate.getRealmKey();
} }
@Override @Override
public PublicKeyLocator getPublicKeyLocator() { public void setRealmKey(PublicKey realmKey) {
return delegate.getPublicKeyLocator(); delegate.setRealmKey(realmKey);
} }
@Override @Override
@@ -342,12 +382,6 @@ public class AdapterDeploymentContext {
return delegate.getNotBefore(); return delegate.getNotBefore();
} }
@Override
public void updateNotBefore(int notBefore) {
delegate.setNotBefore(notBefore);
getPublicKeyLocator().reset(this);
}
@Override @Override
public void setExposeToken(boolean exposeToken) { public void setExposeToken(boolean exposeToken) {
delegate.setExposeToken(exposeToken); delegate.setExposeToken(exposeToken);
@@ -432,36 +466,6 @@ public class AdapterDeploymentContext {
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) { public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive); delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
} }
@Override
public PolicyEnforcer getPolicyEnforcer() {
return delegate.getPolicyEnforcer();
}
@Override
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
delegate.setPolicyEnforcer(policyEnforcer);
}
@Override
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
}
@Override
public int getMinTimeBetweenJwksRequests() {
return delegate.getMinTimeBetweenJwksRequests();
}
@Override
public int getPublicKeyCacheTtl() {
return delegate.getPublicKeyCacheTtl();
}
@Override
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
}
} }
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) { protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
@@ -19,7 +19,9 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.common.util.UriUtils;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
@@ -101,7 +101,6 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false; if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext(); KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN); String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI()); String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI()); log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) { if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@@ -125,9 +124,6 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200); facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else { } else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI()); log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
} }
@@ -146,13 +142,9 @@ public class AuthenticatedActionsHandler {
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade); AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext(); RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
if (session != null) { session.setAuthorizationContext(authorizationContext);
session.setAuthorizationContext(authorizationContext);
return authorizationContext.isGranted(); return authorizationContext.isGranted();
}
return true;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to enforce policy decisions.", e); throw new RuntimeException("Failed to enforce policy decisions.", e);
} }
@@ -31,10 +31,10 @@ import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.util.Base64; import org.keycloak.common.util.Base64;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.util.List; import java.util.List;
@@ -18,7 +18,7 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
@@ -84,7 +84,7 @@ public class BearerTokenRequestAuthenticator {
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) { protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
try { try {
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
} catch (VerificationException e) { } catch (VerificationException e) {
log.error("Failed to verify token", e); log.error("Failed to verify token", e);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage()); challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
@@ -17,17 +17,19 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import java.io.IOException;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -71,7 +73,7 @@ public class CookieTokenStore {
try { try {
// Skip check if token is active now. It's supposed to be done later by the caller // Skip check if token is active now. It's supposed to be done later by the caller
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true); AccessToken accessToken = RSATokenVerifier.verifyToken(accessTokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl(), false, true);
IDToken idToken; IDToken idToken;
if (idTokenString != null && idTokenString.length() > 0) { if (idTokenString != null && idTokenString.length() > 0) {
try { try {
@@ -30,5 +30,4 @@ public interface CorsHeaders {
String ORIGIN = "Origin"; String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
} }
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HttpAdapterUtils {
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
try {
HttpResponse response = deployment.getClient().execute(httpRequest);
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
close(response);
throw new HttpClientAdapterException("Unexpected status = " + status);
}
HttpEntity entity = response.getEntity();
if (entity == null) {
throw new HttpClientAdapterException("There was no entity.");
}
InputStream is = entity.getContent();
try {
return JsonSerialization.readValue(is, clazz);
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
} catch (IOException e) {
throw new HttpClientAdapterException("IO error", e);
}
}
private static void close(HttpResponse response) {
if (response.getEntity() != null) {
try {
response.getEntity().getContent().close();
} catch (IOException e) {
}
}
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HttpClientAdapterException extends Exception {
public HttpClientAdapterException(String message) {
super(message);
}
public HttpClientAdapterException(String message, Throwable t) {
super(message, t);
}
}
@@ -38,7 +38,7 @@ import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpConnectionParams;
import org.keycloak.common.util.EnvUtil; import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this; return this;
} }
public HttpClientBuilder disableCookieCache(boolean disable) { public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = disable; this.disableCookieCache = true;
return this; return this;
} }
@@ -333,8 +333,8 @@ public class HttpClientBuilder {
} }
} }
public HttpClient build(AdapterHttpClientConfig adapterConfig) { public HttpClient build(AdapterConfig adapterConfig) {
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore(); String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) { if (truststorePath != null) {
@@ -379,13 +379,13 @@ public class HttpClientBuilder {
/** /**
* Configures a the proxy to use for auth-server requests if provided. * Configures a the proxy to use for auth-server requests if provided.
* <p> * <p>
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the * If the given {@link AdapterConfig} contains the attribute {@code proxy-url} we use the
* given URL as a proxy server, otherwise the proxy configuration is ignored. * given URL as a proxy server, otherwise the proxy configuration is ignored.
* </p> * </p>
* *
* @param adapterConfig * @param adapterConfig
*/ */
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) { private void configureProxyForAuthServerIfProvided(AdapterConfig adapterConfig) {
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) { if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
return; return;
@@ -21,22 +21,20 @@ import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider; import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer; import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.rotation.PublicKeyLocator; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.net.URI; import java.net.URI;
import java.security.PublicKey;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeycloakDeployment { public class KeycloakDeployment {
@@ -45,7 +43,7 @@ public class KeycloakDeployment {
protected RelativeUrlsUsed relativeUrls; protected RelativeUrlsUsed relativeUrls;
protected String realm; protected String realm;
protected PublicKeyLocator publicKeyLocator; protected volatile PublicKey realmKey;
protected String authServerBaseUrl; protected String authServerBaseUrl;
protected String realmInfoUrl; protected String realmInfoUrl;
protected KeycloakUriBuilder authUrl; protected KeycloakUriBuilder authUrl;
@@ -54,12 +52,10 @@ public class KeycloakDeployment {
protected String accountUrl; protected String accountUrl;
protected String registerNodeUrl; protected String registerNodeUrl;
protected String unregisterNodeUrl; protected String unregisterNodeUrl;
protected String jwksUrl;
protected String principalAttribute = "sub"; protected String principalAttribute = "sub";
protected String resourceName; protected String resourceName;
protected boolean bearerOnly; protected boolean bearerOnly;
protected boolean autodetectBearerOnly;
protected boolean enableBasicAuth; protected boolean enableBasicAuth;
protected boolean publicClient; protected boolean publicClient;
protected Map<String, Object> resourceCredentials = new HashMap<>(); protected Map<String, Object> resourceCredentials = new HashMap<>();
@@ -75,7 +71,6 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1; protected int corsMaxAge = -1;
protected String corsAllowedHeaders; protected String corsAllowedHeaders;
protected String corsAllowedMethods; protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken; protected boolean exposeToken;
protected boolean alwaysRefreshToken; protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup; protected boolean registerNodeAtStartup;
@@ -84,21 +79,13 @@ public class KeycloakDeployment {
protected volatile int notBefore; protected volatile int notBefore;
protected int tokenMinimumTimeToLive; protected int tokenMinimumTimeToLive;
protected int minTimeBetweenJwksRequests;
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer; private PolicyEnforcer policyEnforcer;
// https://tools.ietf.org/html/rfc7636
protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
public KeycloakDeployment() { public KeycloakDeployment() {
} }
public boolean isConfigured() { public boolean isConfigured() {
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null); return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
} }
public String getResourceName() { public String getResourceName() {
@@ -113,12 +100,12 @@ public class KeycloakDeployment {
this.realm = realm; this.realm = realm;
} }
public PublicKeyLocator getPublicKeyLocator() { public PublicKey getRealmKey() {
return publicKeyLocator; return realmKey;
} }
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) { public void setRealmKey(PublicKey realmKey) {
this.publicKeyLocator = publicKeyLocator; this.realmKey = realmKey;
} }
public String getAuthServerBaseUrl() { public String getAuthServerBaseUrl() {
@@ -160,7 +147,6 @@ public class KeycloakDeployment {
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString(); accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString(); registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString(); unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).build(getRealm()).toString();
} }
public RelativeUrlsUsed getRelativeUrls() { public RelativeUrlsUsed getRelativeUrls() {
@@ -195,10 +181,6 @@ public class KeycloakDeployment {
return unregisterNodeUrl; return unregisterNodeUrl;
} }
public String getJwksUrl() {
return jwksUrl;
}
public void setResourceName(String resourceName) { public void setResourceName(String resourceName) {
this.resourceName = resourceName; this.resourceName = resourceName;
} }
@@ -211,14 +193,6 @@ public class KeycloakDeployment {
this.bearerOnly = bearerOnly; this.bearerOnly = bearerOnly;
} }
public boolean isAutodetectBearerOnly() {
return autodetectBearerOnly;
}
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
this.autodetectBearerOnly = autodetectBearerOnly;
}
public boolean isEnableBasicAuth() { public boolean isEnableBasicAuth() {
return enableBasicAuth; return enableBasicAuth;
} }
@@ -331,14 +305,6 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods; this.corsAllowedMethods = corsAllowedMethods;
} }
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() { public boolean isExposeToken() {
return exposeToken; return exposeToken;
} }
@@ -355,11 +321,6 @@ public class KeycloakDeployment {
this.notBefore = notBefore; this.notBefore = notBefore;
} }
public void updateNotBefore(int notBefore) {
this.notBefore = notBefore;
getPublicKeyLocator().reset(this);
}
public boolean isAlwaysRefreshToken() { public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken; return alwaysRefreshToken;
} }
@@ -408,22 +369,6 @@ public class KeycloakDeployment {
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive; this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
} }
public int getMinTimeBetweenJwksRequests() {
return minTimeBetweenJwksRequests;
}
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
}
public int getPublicKeyCacheTtl() {
return publicKeyCacheTtl;
}
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) { public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer; this.policyEnforcer = policyEnforcer;
} }
@@ -431,31 +376,4 @@ public class KeycloakDeployment {
public PolicyEnforcer getPolicyEnforcer() { public PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer; return policyEnforcer;
} }
// https://tools.ietf.org/html/rfc7636
public boolean isPkce() {
return pkce;
}
public void setPkce(boolean pkce) {
this.pkce = pkce;
}
public void setIgnoreOAuthQueryParameter(boolean ignoreOAuthQueryParameter) {
this.ignoreOAuthQueryParameter = ignoreOAuthQueryParameter;
}
public boolean isOAuthQueryParameterEnabled() {
return !this.ignoreOAuthQueryParameter;
}
public Map<String, String> getRedirectRewriteRules() {
return redirectRewriteRules;
}
public void setRewriteRedirectRules(Map<String, String> redirectRewriteRules) {
this.redirectRewriteRules = redirectRewriteRules;
}
} }
@@ -22,8 +22,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils; import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.authorization.PolicyEnforcer; import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
@@ -37,8 +35,6 @@ import java.security.PublicKey;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class KeycloakDeploymentBuilder { public class KeycloakDeploymentBuilder {
@@ -63,16 +59,11 @@ public class KeycloakDeploymentBuilder {
PublicKey realmKey; PublicKey realmKey;
try { try {
realmKey = PemUtils.decodePublicKey(realmKeyPem); realmKey = PemUtils.decodePublicKey(realmKeyPem);
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
deployment.setPublicKeyLocator(pkLocator);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} else { deployment.setRealmKey(realmKey);
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
deployment.setPublicKeyLocator(pkLocator);
} }
if (adapterConfig.getSslRequired() != null) { if (adapterConfig.getSslRequired() != null) {
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase())); deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
} else { } else {
@@ -98,25 +89,14 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge()); deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders()); deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods()); deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
}
// https://tools.ietf.org/html/rfc7636
if (adapterConfig.isPkce()) {
deployment.setPkce(true);
} }
deployment.setBearerOnly(adapterConfig.isBearerOnly()); deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth()); deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken()); deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup()); deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod()); deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive()); deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
deployment.setIgnoreOAuthQueryParameter(adapterConfig.isIgnoreOAuthQueryParameter());
deployment.setRewriteRedirectRules(adapterConfig.getRedirectRewriteRules());
if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) { if (realmKeyPem == null && adapterConfig.isBearerOnly() && adapterConfig.getAuthServerUrl() == null) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url"); throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@@ -125,7 +105,7 @@ public class KeycloakDeploymentBuilder {
deployment.setClient(new HttpClientBuilder().build(adapterConfig)); deployment.setClient(new HttpClientBuilder().build(adapterConfig));
} }
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) { if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
throw new RuntimeException("You must specify auth-server-url"); throw new RuntimeException("You must specify auth-url");
} }
deployment.setAuthServerBaseUrl(adapterConfig); deployment.setAuthServerBaseUrl(adapterConfig);
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) { if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
@@ -17,10 +17,6 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.common.util.HostUtils;
import org.keycloak.common.util.Time;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@@ -28,6 +24,10 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.jboss.logging.Logger;
import org.keycloak.common.util.HostUtils;
import org.keycloak.common.util.Time;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@@ -19,14 +19,12 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.spi.AdapterSessionStore; import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
@@ -34,13 +32,11 @@ import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken; import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil; import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.util.concurrent.atomic.AtomicLong;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
/** /**
@@ -143,7 +139,6 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) { protected String getRedirectUri(String state) {
String url = getRequestUrl(); String url = getRequestUrl();
log.debugf("callback uri: %s", url); log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort(); int port = sslRedirectPort();
if (port < 0) { if (port < 0) {
@@ -164,16 +159,10 @@ public class OAuthRequestAuthenticator {
String scope = getQueryParamValue(OAuth2Constants.SCOPE); String scope = getQueryParamValue(OAuth2Constants.SCOPE);
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE); url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
String prompt = getQueryParamValue(OAuth2Constants.PROMPT);
url = UriUtils.stripQueryParam(url, OAuth2Constants.PROMPT);
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url)) .queryParam(OAuth2Constants.REDIRECT_URI, url)
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true"); .queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){ if(loginHint != null && loginHint.length() > 0){
@@ -182,15 +171,9 @@ public class OAuthRequestAuthenticator {
if (idpHint != null && idpHint.length() > 0) { if (idpHint != null && idpHint.length() > 0) {
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint); redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
} }
if (prompt != null && prompt.length() > 0) { if (scope != null && scope.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt); redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
} }
if (maxAge != null && maxAge.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
}
scope = TokenUtil.attachOIDCScope(scope);
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
return redirectUriBuilder.build().toString(); return redirectUriBuilder.build().toString();
} }
@@ -199,13 +182,15 @@ public class OAuthRequestAuthenticator {
return sslRedirectPort; return sslRedirectPort;
} }
protected static final AtomicLong counter = new AtomicLong();
protected String getStateCode() { protected String getStateCode() {
return AdapterUtils.generateId(); return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
} }
protected AuthChallenge loginRedirect() { protected AuthChallenge loginRedirect() {
final String state = getStateCode(); final String state = getStateCode();
final String redirect = getRedirectUri(state); final String redirect = getRedirectUri(state);
if (redirect == null) { if (redirect == null) {
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null); return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
} }
@@ -323,11 +308,10 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null; AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect(); strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try { try {
// For COOKIE store we don't have httpSessionId and single sign-out won't be available // For COOKIE store we don't have httpSessionId and single sign-out won't be available
String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null; String httpSessionId = deployment.getTokenStore() == TokenStore.SESSION ? reqAuthenticator.changeHttpSessionId(true) : null;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId); tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) { } catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token"); log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus()); log.error("status from server: " + failure.getStatus());
@@ -345,7 +329,7 @@ public class OAuthRequestAuthenticator {
refreshToken = tokenResponse.getRefreshToken(); refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken(); idTokenString = tokenResponse.getIdToken();
try { try {
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) { if (idTokenString != null) {
try { try {
JWSInput input = new JWSInput(idTokenString); JWSInput input = new JWSInput(idTokenString);
@@ -360,7 +344,7 @@ public class OAuthRequestAuthenticator {
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null); return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
} }
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) { if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(tokenResponse.getNotBeforePolicy()); deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
} }
if (token.getIssuedAt() < deployment.getNotBefore()) { if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token"); log.error("Stale token");
@@ -379,23 +363,6 @@ public class OAuthRequestAuthenticator {
.replaceQueryParam(OAuth2Constants.STATE, null); .replaceQueryParam(OAuth2Constants.STATE, null);
return builder.build().toString(); return builder.build().toString();
} }
private String rewrittenRedirectUri(String originalUri) {
Map<String, String> rewriteRules = deployment.getRedirectRewriteRules();
if(rewriteRules != null && !rewriteRules.isEmpty()) {
try {
URL url = new URL(originalUri);
Map.Entry<String, String> rule = rewriteRules.entrySet().iterator().next();
StringBuilder redirectUriBuilder = new StringBuilder(url.getProtocol());
redirectUriBuilder.append("://"+ url.getAuthority());
redirectUriBuilder.append(url.getPath().replaceFirst(rule.getKey(), rule.getValue()));
return redirectUriBuilder.toString();
} catch (MalformedURLException ex) {
log.error("Not a valid request url");
throw new RuntimeException(ex);
}
}
return originalUri;
}
} }
@@ -35,8 +35,7 @@ public class OIDCAuthenticationError implements AuthenticationError {
CODE_TO_TOKEN_FAILURE, CODE_TO_TOKEN_FAILURE,
INVALID_TOKEN, INVALID_TOKEN,
STALE_TOKEN, STALE_TOKEN,
NO_AUTHORIZATION_HEADER, NO_AUTHORIZATION_HEADER
NO_QUERY_PARAMETER_ACCESS_TOKEN
} }
private Reason reason; private Reason reason;
@@ -17,32 +17,20 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import java.security.PublicKey;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.UserSessionManagement; import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.VersionRepresentation; import org.keycloak.representations.VersionRepresentation;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider; import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.representations.VersionRepresentation;
import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.PushNotBeforeAction; import org.keycloak.representations.adapters.action.PushNotBeforeAction;
import org.keycloak.representations.adapters.action.TestAvailabilityAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.StreamUtil;
import java.security.PublicKey;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -94,10 +82,6 @@ public class PreAuthActionsHandler {
if (!resolveDeployment()) return true; if (!resolveDeployment()) return true;
handleTestAvailable(); handleTestAvailable();
return true; return true;
} else if (requestUri.endsWith(AdapterConstants.K_JWKS)) {
if (!resolveDeployment()) return true;
handleJwksRequest();
return true;
} }
return false; return false;
} }
@@ -155,7 +139,7 @@ public class PreAuthActionsHandler {
} else { } else {
log.debugf("logout of all sessions for application '%s'", action.getResource()); log.debugf("logout of all sessions for application '%s'", action.getResource());
if (action.getNotBefore() > deployment.getNotBefore()) { if (action.getNotBefore() > deployment.getNotBefore()) {
deployment.updateNotBefore(action.getNotBefore()); deployment.setNotBefore(action.getNotBefore());
} }
userSessionManagement.logoutAll(); userSessionManagement.logoutAll();
} }
@@ -177,7 +161,7 @@ public class PreAuthActionsHandler {
} }
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class); PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
if (!validateAction(action)) return; if (!validateAction(action)) return;
deployment.updateNotBefore(action.getNotBefore()); deployment.setNotBefore(action.getNotBefore());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -214,8 +198,7 @@ public class PreAuthActionsHandler {
try { try {
JWSInput input = new JWSInput(token); JWSInput input = new JWSInput(token);
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment); if (RSAProvider.verify(input, deployment.getRealmKey())) {
if (RSAProvider.verify(input, publicKey)) {
return input; return input;
} }
} catch (JWSInputException ignore) { } catch (JWSInputException ignore) {
@@ -257,26 +240,4 @@ public class PreAuthActionsHandler {
} }
} }
protected void handleJwksRequest() {
try {
JSONWebKeySet jwks = new JSONWebKeySet();
ClientCredentialsProvider clientCredentialsProvider = deployment.getClientAuthenticator();
// For now, just get signature key from JWT provider. We can add more if we support encryption etc.
if (clientCredentialsProvider instanceof JWTClientCredentialsProvider) {
PublicKey publicKey = ((JWTClientCredentialsProvider) clientCredentialsProvider).getPublicKey();
JWK jwk = JWKBuilder.create().rs256(publicKey);
jwks.setKeys(new JWK[] { jwk });
} else {
jwks.setKeys(new JWK[] {});
}
facade.getResponse().setStatus(200);
facade.getResponse().setHeader("Content-Type", "application/json");
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), jwks);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }
@@ -1,59 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
* @version $Revision: 1 $
*/
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
public static final String ACCESS_TOKEN = "access_token";
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
super(deployment);
}
public AuthOutcome authenticate(HttpFacade exchange) {
if(!deployment.isOAuthQueryParameterEnabled()) {
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
return (authenticateToken(exchange, tokenString));
}
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
try {
if (exchange != null && exchange.getRequest() != null) {
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
}
} catch (Exception ignore) {
}
return null;
}
}
@@ -20,7 +20,7 @@ package org.keycloak.adapters;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import org.keycloak.RSATokenVerifier;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@@ -130,7 +130,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
String tokenString = response.getToken(); String tokenString = response.getToken();
AccessToken token = null; AccessToken token = null;
try { try {
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
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");
@@ -144,7 +144,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
} }
if (response.getNotBeforePolicy() > deployment.getNotBefore()) { if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.updateNotBefore(response.getNotBeforePolicy()); deployment.setNotBefore(response.getNotBeforePolicy());
} }
this.token = token; this.token = token;
@@ -17,14 +17,11 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import java.util.Collections;
import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge; import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome; import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.SslRequired;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -64,7 +61,7 @@ public abstract class RequestAuthenticator {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("try bearer"); log.trace("try bearer");
} }
AuthOutcome outcome = bearer.authenticate(facade); AuthOutcome outcome = bearer.authenticate(facade);
if (outcome == AuthOutcome.FAILED) { if (outcome == AuthOutcome.FAILED) {
challenge = bearer.getChallenge(); challenge = bearer.getChallenge();
@@ -77,36 +74,18 @@ public abstract class RequestAuthenticator {
return AuthOutcome.AUTHENTICATED; return AuthOutcome.AUTHENTICATED;
} }
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try query paramter auth");
}
outcome = queryParamAuth.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = queryParamAuth.getChallenge();
log.debug("QueryParamAuth auth FAILED");
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
if (verifySSL()) return AuthOutcome.FAILED;
log.debug("QueryParamAuth AUTHENTICATED");
completeAuthentication(queryParamAuth, "KEYCLOAK");
return AuthOutcome.AUTHENTICATED;
}
if (deployment.isEnableBasicAuth()) { if (deployment.isEnableBasicAuth()) {
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator(); BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("try basic auth"); log.trace("try basic auth");
} }
outcome = basicAuth.authenticate(facade); outcome = basicAuth.authenticate(facade);
if (outcome == AuthOutcome.FAILED) { if (outcome == AuthOutcome.FAILED) {
challenge = basicAuth.getChallenge(); challenge = basicAuth.getChallenge();
log.debug("BasicAuth FAILED"); log.debug("BasicAuth FAILED");
return AuthOutcome.FAILED; return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) { } else if (outcome == AuthOutcome.AUTHENTICATED) {
if (verifySSL()) return AuthOutcome.FAILED;
log.debug("BasicAuth AUTHENTICATED"); log.debug("BasicAuth AUTHENTICATED");
completeAuthentication(basicAuth, "BASIC"); completeAuthentication(basicAuth, "BASIC");
return AuthOutcome.AUTHENTICATED; return AuthOutcome.AUTHENTICATED;
@@ -119,12 +98,6 @@ public abstract class RequestAuthenticator {
return AuthOutcome.NOT_ATTEMPTED; return AuthOutcome.NOT_ATTEMPTED;
} }
if (isAutodetectedBearerOnly(facade.getRequest())) {
challenge = bearer.getChallenge();
log.debug("NOT_ATTEMPTED: Treating as bearer only");
return AuthOutcome.NOT_ATTEMPTED;
}
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("try oauth"); log.trace("try oauth");
} }
@@ -161,43 +134,12 @@ public abstract class RequestAuthenticator {
protected boolean verifySSL() { protected boolean verifySSL() {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) { if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warnf("SSL is required to authenticate. Remote address %s is secure: %s, SSL required for: %s .", log.warn("SSL is required to authenticate");
facade.getRequest().getRemoteAddr(), facade.getRequest().isSecure(), deployment.getSslRequired().name());
return true; return true;
} }
return false; return false;
} }
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
if (!deployment.isAutodetectBearerOnly()) return false;
String headerValue = facade.getRequest().getHeader("X-Requested-With");
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
return true;
}
headerValue = facade.getRequest().getHeader("Faces-Request");
if (headerValue != null && headerValue.startsWith("partial/")) {
return true;
}
headerValue = facade.getRequest().getHeader("SOAPAction");
if (headerValue != null) {
return true;
}
List<String> accepts = facade.getRequest().getHeaders("Accept");
if (accepts == null) accepts = Collections.emptyList();
for (String accept : accepts) {
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
return false;
}
}
return true;
}
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator(); protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() { protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
@@ -208,10 +150,6 @@ public abstract class RequestAuthenticator {
return new BasicAuthRequestAuthenticator(deployment); return new BasicAuthRequestAuthenticator(deployment);
} }
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
return new QueryParamterTokenRequestAuthenticator(deployment);
}
protected void completeAuthentication(OAuthRequestAuthenticator oauth) { protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken()); RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session); final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
@@ -220,12 +158,10 @@ public abstract class RequestAuthenticator {
} }
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal); protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method); protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
/** /**
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation * After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
*
* @param create * @param create
* @return * @return
*/ */
@@ -26,14 +26,12 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils; import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.common.util.HostUtils;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.common.util.HostUtils;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.jboss.logging.Logger; import org.keycloak.common.util.StreamUtil;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -48,8 +46,6 @@ import java.util.List;
*/ */
public class ServerRequest { public class ServerRequest {
private static Logger logger = Logger.getLogger(ServerRequest.class);
public static class HttpFailure extends Exception { public static class HttpFailure extends Exception {
private int status; private int status;
private String error; private String error;
@@ -140,62 +136,6 @@ public class ServerRequest {
} }
} }
// https://tools.ietf.org/html/rfc7636#section-4
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId, String codeVerifier) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<>();
redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
if (sessionId != null) {
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
}
// https://tools.ietf.org/html/rfc7636#section-4
if (codeVerifier != null) {
logger.debugf("add to POST parameters of Token Request, codeVerifier = %s", codeVerifier);
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE_VERIFIER, codeVerifier));
} else {
logger.debug("add to POST parameters of Token Request without codeVerifier");
}
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = deployment.getClient().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 {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
try {
return JsonSerialization.readValue(json, AccessTokenResponse.class);
} catch (IOException e) {
throw new IOException(json, e);
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure { public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
@@ -17,10 +17,10 @@
package org.keycloak.adapters.authentication; package org.keycloak.adapters.authentication;
import org.keycloak.adapters.KeycloakDeployment;
import java.util.Map; import java.util.Map;
import org.keycloak.adapters.KeycloakDeployment;
/** /**
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server * The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request * (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
@@ -17,12 +17,6 @@
package org.keycloak.adapters.authentication; package org.keycloak.adapters.authentication;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -30,6 +24,12 @@ import java.util.Map;
import java.util.ServiceConfigurationError; import java.util.ServiceConfigurationError;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/ */
@@ -17,14 +17,14 @@
package org.keycloak.adapters.authentication; package org.keycloak.adapters.authentication;
import java.util.Map;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper; import org.keycloak.util.BasicAuthHelper;
import java.util.Map;
/** /**
* Traditional OAuth2 authentication of clients based on client_id and client_secret * Traditional OAuth2 authentication of clients based on client_id and client_secret
* *
@@ -17,24 +17,16 @@
package org.keycloak.adapters.authentication; package org.keycloak.adapters.authentication;
import java.security.KeyPair; import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map; import java.util.Map;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.jose.jwk.JWKBuilder;
import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import org.keycloak.common.util.KeystoreUtil; import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import java.security.PrivateKey;
import java.util.Map;
/** /**
* Client authentication based on JWT signed by client private key . * Client authentication based on JWT signed by client private key .
@@ -46,9 +38,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public static final String PROVIDER_ID = "jwt"; public static final String PROVIDER_ID = "jwt";
private KeyPair keyPair; private PrivateKey privateKey;
private JWK publicKeyJwk;
private int tokenTimeout; private int tokenTimeout;
@Override @Override
@@ -56,23 +46,14 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
return PROVIDER_ID; return PROVIDER_ID;
} }
public void setupKeyPair(KeyPair keyPair) { public void setPrivateKey(PrivateKey privateKey) {
this.keyPair = keyPair; this.privateKey = privateKey;
this.publicKeyJwk = JWKBuilder.create().rs256(keyPair.getPublic());
} }
public void setTokenTimeout(int tokenTimeout) { public void setTokenTimeout(int tokenTimeout) {
this.tokenTimeout = tokenTimeout; this.tokenTimeout = tokenTimeout;
} }
protected int getTokenTimeout() {
return tokenTimeout;
}
public PublicKey getPublicKey() {
return keyPair.getPublic();
}
@Override @Override
public void init(KeycloakDeployment deployment, Object config) { public void init(KeycloakDeployment deployment, Object config) {
if (config == null || !(config instanceof Map)) { if (config == null || !(config instanceof Map)) {
@@ -103,9 +84,7 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
if (clientKeyAlias == null) { if (clientKeyAlias == null) {
clientKeyAlias = deployment.getResourceName(); clientKeyAlias = deployment.getResourceName();
} }
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
KeyPair keyPair = KeystoreUtil.loadKeyPairFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
setupKeyPair(keyPair);
this.tokenTimeout = asInt(cfg, "token-timeout", 10); this.tokenTimeout = asInt(cfg, "token-timeout", 10);
} }
@@ -137,9 +116,8 @@ public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public String createSignedRequestToken(String clientId, String realmInfoUrl) { public String createSignedRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl); JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
return new JWSBuilder() return new JWSBuilder()
.kid(publicKeyJwk.getKeyId())
.jsonContent(jwt) .jsonContent(jwt)
.rsa256(keyPair.getPrivate()); .rsa256(privateKey);
} }
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) { protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
@@ -17,24 +17,25 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Request; import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.adapters.spi.HttpFacade.Response;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthorizationContext; import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.authorization.Permission;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -45,7 +46,7 @@ public abstract class AbstractPolicyEnforcer {
private final PolicyEnforcerConfig enforcerConfig; private final PolicyEnforcerConfig enforcerConfig;
private final PolicyEnforcer policyEnforcer; private final PolicyEnforcer policyEnforcer;
private Map<String, PathConfig> paths; private List<PathConfig> paths;
private AuthzClient authzClient; private AuthzClient authzClient;
private PathMatcher pathMatcher; private PathMatcher pathMatcher;
@@ -53,7 +54,7 @@ public abstract class AbstractPolicyEnforcer {
this.policyEnforcer = policyEnforcer; this.policyEnforcer = policyEnforcer;
this.enforcerConfig = policyEnforcer.getEnforcerConfig(); this.enforcerConfig = policyEnforcer.getEnforcerConfig();
this.authzClient = policyEnforcer.getClient(); this.authzClient = policyEnforcer.getClient();
this.pathMatcher = policyEnforcer.getPathMatcher(); this.pathMatcher = new PathMatcher();
this.paths = policyEnforcer.getPaths(); this.paths = policyEnforcer.getPaths();
} }
@@ -64,55 +65,40 @@ public abstract class AbstractPolicyEnforcer {
return createEmptyAuthorizationContext(true); return createEmptyAuthorizationContext(true);
} }
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext(); AccessToken accessToken = httpFacade.getSecurityContext().getToken();
Request request = httpFacade.getRequest();
Response response = httpFacade.getResponse();
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
if (securityContext != null) { LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
AccessToken accessToken = securityContext.getToken();
if (accessToken != null) { if (pathConfig == null) {
Request request = httpFacade.getRequest(); if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
String path = getPath(request); return createAuthorizationContext(accessToken);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken, null);
}
LOGGER.debugf("Could not find a configuration for path [%s]", path);
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return createAuthorizationContext(accessToken, null);
}
handleAccessDenied(httpFacade);
return createEmptyAuthorizationContext(false);
}
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
}
Set<String> requiredScopes = getRequiredScopes(pathConfig, request);
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
}
}
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
if (!challenge(pathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
handleAccessDenied(httpFacade);
}
} }
LOGGER.debugf("Could not find a configuration for path [%s]", path);
response.sendError(403, "Could not find a configuration for path [" + path + "].");
return createEmptyAuthorizationContext(false);
}
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
}
}
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
} }
return createEmptyAuthorizationContext(false); return createEmptyAuthorizationContext(false);
@@ -123,9 +109,12 @@ public abstract class AbstractPolicyEnforcer {
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) { protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest(); Request request = httpFacade.getRequest();
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig(); PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
String accessDeniedPath = enforcerConfig.getAccessDeniedPath();
if (isDefaultAccessDeniedUri(request, enforcerConfig)) { if (accessDeniedPath != null) {
return true; if (request.getURI().contains(accessDeniedPath)) {
return true;
}
} }
AccessToken.Authorization authorization = accessToken.getAuthorization(); AccessToken.Authorization authorization = accessToken.getAuthorization();
@@ -135,18 +124,13 @@ public abstract class AbstractPolicyEnforcer {
} }
List<Permission> permissions = authorization.getPermissions(); List<Permission> permissions = authorization.getPermissions();
boolean hasPermission = false;
for (Permission permission : permissions) { for (Permission permission : permissions) {
Set<String> allowedScopes = permission.getScopes();
if (permission.getResourceSetId() != null) { if (permission.getResourceSetId() != null) {
if (isResourcePermission(actualPathConfig, permission)) { if (isResourcePermission(actualPathConfig, permission)) {
hasPermission = true; if (((allowedScopes == null || allowedScopes.isEmpty()) && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
continue;
}
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions); LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) { if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
this.paths.remove(actualPathConfig); this.paths.remove(actualPathConfig);
@@ -155,42 +139,18 @@ public abstract class AbstractPolicyEnforcer {
} }
} }
} else { } else {
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) { if ((allowedScopes.isEmpty() && requiredScopes.isEmpty()) || allowedScopes.containsAll(requiredScopes)) {
hasPermission = true; LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
return true; return true;
} }
} }
} }
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
return true;
}
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions); LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
return false; return false;
} }
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
httpFacade.getResponse().sendError(403);
}
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
return false;
}
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
Set<String> allowedScopes = permission.getScopes();
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
}
protected AuthzClient getAuthzClient() { protected AuthzClient getAuthzClient() {
return this.authzClient; return this.authzClient;
} }
@@ -204,7 +164,7 @@ public abstract class AbstractPolicyEnforcer {
} }
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) { private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(authzClient) { return new AuthorizationContext() {
@Override @Override
public boolean hasPermission(String resourceName, String scopeName) { public boolean hasPermission(String resourceName, String scopeName) {
return granted; return granted;
@@ -232,8 +192,34 @@ public abstract class AbstractPolicyEnforcer {
}; };
} }
private String getPath(Request request) { private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
return request.getRelativePath(); if (originalConfig.hasPattern()) {
String pathInfo = URI.create(request.getURI()).getPath().substring(1);
String path = pathInfo.substring(pathInfo.indexOf('/'), pathInfo.length());
ProtectedResource resource = this.authzClient.protection().resource();
Set<String> search = resource.findByFilter("uri=" + path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
PathConfig config = new PathConfig();
config.setId(targetResource.getId());
config.setName(targetResource.getName());
config.setType(targetResource.getType());
config.setPath(targetResource.getUri());
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setInstance(true);
config.setParentConfig(originalConfig);
this.paths.add(config);
return config;
}
}
return originalConfig;
} }
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) { private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
@@ -252,23 +238,19 @@ public abstract class AbstractPolicyEnforcer {
return requiredScopes; return requiredScopes;
} }
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) { private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient); return new AuthorizationContext(accessToken, this.paths);
} }
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) { private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
// first we try a match using resource id // first we try a match using resource id
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission); boolean resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getId());
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission // as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
if (!resourceMatch && actualPathConfig.isInstance()) { if (!resourceMatch && actualPathConfig.isInstance()) {
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission); resourceMatch = permission.getResourceSetId().equals(actualPathConfig.getParentConfig().getId());
} }
return resourceMatch; return resourceMatch;
} }
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
return permission.getResourceSetId().equals(actualPathConfig.getId());
}
} }
@@ -17,8 +17,6 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
@@ -28,6 +26,9 @@ import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource; import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.List;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
@@ -41,7 +42,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
@Override @Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) { protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
if (getEnforcerConfig().getUserManagedAccess() != null) { if (getEnforcerConfig().getUmaProtocolConfig() != null) {
challengeUmaAuthentication(pathConfig, requiredScopes, facade); challengeUmaAuthentication(pathConfig, requiredScopes, facade);
} else { } else {
challengeEntitlementAuthentication(facade); challengeEntitlementAuthentication(facade);
@@ -52,26 +53,20 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) { private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
String clientId = authzClient.getConfiguration().getResource(); String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement"; String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
response.setStatus(401); response.setStatus(401);
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\""); response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending Entitlement challenge");
}
} }
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) { private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient); String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
String clientId = authzClient.getConfiguration().getResource(); String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize"; String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401); response.setStatus(401);
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\""); response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending UMA challenge");
}
} }
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) { private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
@@ -17,26 +17,22 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.AuthorizationDeniedException; import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.representation.AuthorizationRequest; import org.keycloak.authorization.client.representation.AuthorizationRequest;
import org.keycloak.authorization.client.representation.AuthorizationResponse; import org.keycloak.authorization.client.representation.AuthorizationResponse;
import org.keycloak.authorization.client.representation.EntitlementRequest;
import org.keycloak.authorization.client.representation.EntitlementResponse; import org.keycloak.authorization.client.representation.EntitlementResponse;
import org.keycloak.authorization.client.representation.PermissionRequest; import org.keycloak.authorization.client.representation.PermissionRequest;
import org.keycloak.authorization.client.representation.PermissionResponse; import org.keycloak.authorization.client.representation.PermissionResponse;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
import java.util.Set;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -51,45 +47,30 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
@Override @Override
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) { protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
int retry = 2;
AccessToken original = accessToken; AccessToken original = accessToken;
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) { while (retry >= 0) {
return true; if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
original.setAuthorization(accessToken.getAuthorization());
return true;
}
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
if (accessToken == null) {
return false;
}
retry--;
} }
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade); return false;
if (accessToken == null) {
return false;
}
AccessToken.Authorization authorization = original.getAuthorization();
if (authorization == null) {
authorization = new AccessToken.Authorization();
authorization.setPermissions(new ArrayList<Permission>());
}
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
if (newAuthorization != null) {
authorization.getPermissions().addAll(newAuthorization.getPermissions());
}
original.setAuthorization(authorization);
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
} }
@Override @Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) { protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
handleAccessDenied(facade); String accessDeniedPath = getEnforcerConfig().getAccessDeniedPath();
return true;
}
@Override
protected void handleAccessDenied(OIDCHttpFacade facade) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
HttpFacade.Response response = facade.getResponse(); HttpFacade.Response response = facade.getResponse();
if (accessDeniedPath != null) { if (accessDeniedPath != null) {
@@ -98,6 +79,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
} else { } else {
response.sendError(403); response.sendError(403);
} }
return true;
} }
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) { private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
@@ -106,8 +89,8 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AuthzClient authzClient = getAuthzClient(); AuthzClient authzClient = getAuthzClient();
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment(); KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
if (getEnforcerConfig().getUserManagedAccess() != null) { if (getEnforcerConfig().getUmaProtocolConfig() != null) {
LOGGER.debug("Obtaining authorization for authenticated user."); LOGGER.debug("Obtaining authorization for authenticated user.");
PermissionRequest permissionRequest = new PermissionRequest(); PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId()); permissionRequest.setResourceSetId(pathConfig.getId());
@@ -118,31 +101,16 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest); AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
if (authzResponse != null) { if (authzResponse != null) {
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment); return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
} }
return null; return null;
} else { } else {
LOGGER.debug("Obtaining entitlements for authenticated user."); LOGGER.debug("Obtaining entitlements for authenticated user.");
AccessToken token = httpFacade.getSecurityContext().getToken(); EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
return RSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (token.getAuthorization() == null) {
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
} else {
EntitlementRequest request = new EntitlementRequest();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setResourceSetName(pathConfig.getName());
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
request.addPermission(permissionRequest);
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
} }
} catch (AuthorizationDeniedException e) { } catch (AuthorizationDeniedException e) {
LOGGER.debug("Authorization denied", e);
return null; return null;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Unexpected error during authorization request.", e); throw new RuntimeException("Unexpected error during authorization request.", e);
@@ -1,170 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.authorization;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PathCache {
/**
* The load factor.
*/
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private final Map<String, CacheEntry> cache;
private final AtomicBoolean writing = new AtomicBoolean(false);
private final long maxAge;
/**
* Creates a new instance.
*
* @param maxEntries the maximum number of entries to keep in the cache
*/
public PathCache(int maxEntries) {
this(maxEntries, -1);
}
/**
* Creates a new instance.
*
* @param maxEntries the maximum number of entries to keep in the cache
* @param maxAge the time in milliseconds that an entry can stay in the cache. If {@code -1}, entries never expire
*/
public PathCache(final int maxEntries, long maxAge) {
cache = new LinkedHashMap<String, CacheEntry>(16, DEFAULT_LOAD_FACTOR, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return cache.size() > maxEntries;
}
};
this.maxAge = maxAge;
}
public void put(String uri, PathConfig newValue) {
try {
if (parkForWriteAndCheckInterrupt()) {
return;
}
CacheEntry cacheEntry = cache.get(uri);
if (cacheEntry == null) {
cache.put(uri, new CacheEntry(uri, newValue, maxAge));
}
} finally {
writing.lazySet(false);
}
}
public PathConfig get(String uri) {
if (parkForReadAndCheckInterrupt()) {
return null;
}
CacheEntry cached = cache.get(uri);
if (cached != null) {
return removeIfExpired(cached);
}
return null;
}
public void remove(String key) {
try {
if (parkForWriteAndCheckInterrupt()) {
return;
}
cache.remove(key);
} finally {
writing.lazySet(false);
}
}
private PathConfig removeIfExpired(CacheEntry cached) {
if (cached == null) {
return null;
}
if (cached.isExpired()) {
remove(cached.key());
return null;
}
return cached.value();
}
private boolean parkForWriteAndCheckInterrupt() {
while (!writing.compareAndSet(false, true)) {
LockSupport.parkNanos(1L);
if (Thread.interrupted()) {
return true;
}
}
return false;
}
private boolean parkForReadAndCheckInterrupt() {
while (writing.get()) {
LockSupport.parkNanos(1L);
if (Thread.interrupted()) {
return true;
}
}
return false;
}
private static final class CacheEntry {
final String key;
final PathConfig value;
final long expiration;
CacheEntry(String key, PathConfig value, long maxAge) {
this.key = key;
this.value = value;
if(maxAge == -1) {
expiration = -1;
} else {
expiration = System.currentTimeMillis() + maxAge;
}
}
String key() {
return key;
}
PathConfig value() {
return value;
}
boolean isExpired() {
return expiration != -1 ? System.currentTimeMillis() > expiration : false;
}
}
}
@@ -17,226 +17,87 @@
*/ */
package org.keycloak.adapters.authorization; package org.keycloak.adapters.authorization;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.List;
/** /**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/ */
class PathMatcher { class PathMatcher {
private static final char WILDCARD = '*'; private static final String ANY_RESOURCE_PATTERN = "/*";
private final AuthzClient authzClient;
// TODO: make this configurable
private PathCache cache = new PathCache(100, 30000);
public PathMatcher(AuthzClient authzClient) { PathConfig matches(final String requestedUri, List<PathConfig> paths) {
this.authzClient = authzClient; PathConfig actualConfig = null;
}
public PathConfig matches(final String targetUri, Map<String, PathConfig> paths) { for (PathConfig entry : paths) {
PathConfig pathConfig = paths.get(targetUri) == null ? cache.get(targetUri) : paths.get(targetUri); String protectedUri = entry.getPath();
String selectedUri = null;
if (pathConfig != null) { if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
return pathConfig; selectedUri = protectedUri;
}
PathConfig matchingAnyPath = null;
PathConfig matchingAnySuffixPath = null;
for (PathConfig entry : paths.values()) {
String expectedUri = entry.getPath();
String matchingUri = null;
if (exactMatch(expectedUri, targetUri, expectedUri)) {
matchingUri = expectedUri;
} }
if (isTemplate(expectedUri)) { int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
String templateUri = buildUriFromTemplate(expectedUri, targetUri);
if (templateUri != null) { if (suffixIndex != -1) {
if (exactMatch(expectedUri, targetUri, templateUri)) { String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
matchingUri = templateUri;
entry = resolvePathConfig(entry, targetUri); if (requestedUri.endsWith(protectedSuffix)) {
} selectedUri = protectedUri;
} }
} }
if (matchingUri != null) { if (protectedUri.equals(requestedUri)) {
StringBuilder path = new StringBuilder(expectedUri); selectedUri = protectedUri;
int patternIndex = path.indexOf("/" + WILDCARD); }
if (patternIndex != -1) { if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
path.delete(patternIndex, path.length()); String formattedPattern = removeWildCardsFromUri(protectedUri);
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
selectedUri = protectedUri;
} }
patternIndex = path.indexOf("{"); if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
selectedUri = protectedUri;
if (patternIndex != -1) {
path.delete(patternIndex, path.length());
} }
}
String pathString = path.toString(); int startRegex = protectedUri.indexOf('{');
if ("".equals(pathString)) { if (startRegex != -1) {
pathString = "/"; String prefix = protectedUri.substring(0, startRegex);
if (requestedUri.startsWith(prefix)) {
selectedUri = protectedUri;
} }
}
if (matchingUri.equals(targetUri)) { if (selectedUri != null) {
cache.put(targetUri, entry); selectedUri = protectedUri;
return entry; }
}
if (WILDCARD == expectedUri.charAt(expectedUri.length() - 1)) { if (selectedUri != null) {
matchingAnyPath = entry; if (actualConfig == null) {
actualConfig = entry;
} else { } else {
int suffixIndex = expectedUri.indexOf(WILDCARD + "."); if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
actualConfig = entry;
}
if (suffixIndex != -1) { if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
String protectedSuffix = expectedUri.substring(suffixIndex + 1); actualConfig = entry;
if (targetUri.endsWith(protectedSuffix)) {
matchingAnySuffixPath = entry;
}
} }
} }
} }
} }
if (matchingAnySuffixPath != null) { return actualConfig;
cache.put(targetUri, matchingAnySuffixPath);
return matchingAnySuffixPath;
}
if (matchingAnyPath != null) {
cache.put(targetUri, matchingAnyPath);
}
return matchingAnyPath;
} }
private boolean exactMatch(String expectedUri, String targetUri, String value) { private String removeWildCardsFromUri(String protectedUri) {
if (targetUri.equals(value)) { return protectedUri.replaceAll("/[*]", "/");
return value.equals(targetUri);
}
if (endsWithWildcard(expectedUri)) {
return targetUri.startsWith(expectedUri.substring(0, expectedUri.length() - 2));
}
String suffix = "/*.";
int suffixIndex = expectedUri.indexOf(suffix);
if (suffixIndex != -1) {
return targetUri.endsWith(expectedUri.substring(suffixIndex + suffix.length() - 1));
}
return false;
}
public String buildUriFromTemplate(String expectedUri, String targetUri) {
int patternStartIndex = expectedUri.indexOf("{");
if (patternStartIndex == -1 || patternStartIndex >= targetUri.length()) {
return null;
}
if (expectedUri.split("/").length > targetUri.split("/").length) {
return null;
}
char[] expectedUriChars = expectedUri.toCharArray();
char[] matchingUri = Arrays.copyOfRange(expectedUriChars, 0, patternStartIndex);
int matchingUriLastIndex = matchingUri.length;
String targetUriParams = targetUri.substring(patternStartIndex);
if (Arrays.equals(matchingUri, Arrays.copyOf(targetUri.toCharArray(), matchingUri.length))) {
matchingUri = Arrays.copyOf(matchingUri, targetUri.length());
int paramIndex = 0;
for (int i = patternStartIndex; i < expectedUriChars.length; i++) {
if (matchingUriLastIndex >= matchingUri.length) {
break;
}
char c = expectedUriChars[i];
if (c == '{' || c == '*') {
String[] params = targetUriParams.split("/");
for (int k = paramIndex; k <= (c == '*' ? params.length : paramIndex); k++) {
if (k == params.length) {
break;
}
int paramLength = params[k].length();
if (matchingUriLastIndex + paramLength > matchingUri.length) {
return null;
}
for (int j = 0; j < paramLength; j++) {
matchingUri[matchingUriLastIndex++] = params[k].charAt(j);
}
if (c == '*' && matchingUriLastIndex < matchingUri.length) {
matchingUri[matchingUriLastIndex++] = '/';
}
}
i = expectedUri.indexOf('}', i);
} else {
if (c == '/') {
paramIndex++;
}
matchingUri[matchingUriLastIndex++] = c;
}
}
if (matchingUri[matchingUri.length - 1] == '\u0000') {
return null;
}
return String.valueOf(matchingUri);
}
return null;
}
public boolean endsWithWildcard(String expectedUri) {
return WILDCARD == expectedUri.charAt(expectedUri.length() - 1);
}
private boolean isTemplate(String uri) {
return uri.indexOf("{") != -1;
}
private PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
if (originalConfig.hasPattern()) {
ProtectedResource resource = this.authzClient.protection().resource();
Set<String> search = resource.findByFilter("uri=" + path);
if (!search.isEmpty()) {
// resource does exist on the server, cache it
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
return config;
}
}
return originalConfig;
} }
} }
@@ -21,9 +21,7 @@ import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext; import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient; import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration; import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse; import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation; import org.keycloak.authorization.client.representation.ResourceRepresentation;
@@ -32,14 +30,11 @@ import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission; import org.keycloak.representations.authorization.Permission;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@@ -50,26 +45,21 @@ public class PolicyEnforcer {
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class); private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
private final KeycloakDeployment deployment; private final KeycloakDeployment deployment;
private final PathMatcher pathMatcher;
private final AuthzClient authzClient; private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig; private final PolicyEnforcerConfig enforcerConfig;
private final Map<String, PathConfig> paths; private final List<PathConfig> paths;
private final PathMatcher pathMatcher;
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) { public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment; this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig(); this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()), new ClientAuthenticator() { this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
@Override this.pathMatcher = new PathMatcher();
public void configureClientCredentials(HashMap<String, String> requestParams, HashMap<String, String> requestHeaders) {
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, requestParams);
}
});
this.pathMatcher = new PathMatcher(this.authzClient);
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig); this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
if (LOGGER.isDebugEnabled()) { if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:"); LOGGER.debug("Initialization complete. Path configurations:");
for (PathConfig pathConfig : this.paths.values()) { for (PathConfig pathConfig : this.paths) {
LOGGER.debug(pathConfig); LOGGER.debug(pathConfig);
} }
} }
@@ -107,29 +97,16 @@ public class PolicyEnforcer {
return authzClient; return authzClient;
} }
public Map<String, PathConfig> getPaths() { List<PathConfig> getPaths() {
return paths; return paths;
} }
void addPath(PathConfig pathConfig) {
paths.put(pathConfig.getPath(), pathConfig);
}
KeycloakDeployment getDeployment() { KeycloakDeployment getDeployment() {
return deployment; return deployment;
} }
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) { private List<PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
boolean loadPathsFromServer = true; if (enforcerConfig.getPaths().isEmpty()) {
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
loadPathsFromServer = false;
break;
}
}
if (loadPathsFromServer) {
LOGGER.info("No path provided in configuration."); LOGGER.info("No path provided in configuration.");
return configureAllPathsForResourceServer(protectedResource); return configureAllPathsForResourceServer(protectedResource);
} else { } else {
@@ -138,8 +115,8 @@ public class PolicyEnforcer {
} }
} }
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) { private List<PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>()); List<PathConfig> paths = new ArrayList<>();
for (PathConfig pathConfig : enforcerConfig.getPaths()) { for (PathConfig pathConfig : enforcerConfig.getPaths()) {
Set<String> search; Set<String> search;
@@ -179,62 +156,40 @@ public class PolicyEnforcer {
pathConfig.setId(registrationResponse.getId()); pathConfig.setId(registrationResponse.getId());
} else { } else {
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration."); throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + ". Make sure you have created a resource on the server that matches with the path configuration.");
} }
} else { } else {
pathConfig.setId(search.iterator().next()); pathConfig.setId(search.iterator().next());
} }
PathConfig existingPath = null; paths.add(pathConfig);
for (PathConfig current : paths.values()) {
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
existingPath = current;
break;
}
}
if (existingPath == null) {
paths.put(pathConfig.getPath(), pathConfig);
} else {
existingPath.getMethods().addAll(pathConfig.getMethods());
existingPath.getScopes().addAll(pathConfig.getScopes());
}
} }
return paths; return paths;
} }
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) { private List<PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
LOGGER.info("Querying the server for all resources associated with this application."); LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>()); List<PathConfig> paths = new ArrayList<>();
for (String id : protectedResource.findAll()) { for (String id : protectedResource.findAll()) {
RegistrationResponse response = protectedResource.findById(id); RegistrationResponse response = protectedResource.findById(id);
ResourceRepresentation resourceDescription = response.getResourceDescription(); ResourceRepresentation resourceDescription = response.getResourceDescription();
if (resourceDescription.getUri() != null) { if (resourceDescription.getUri() != null) {
PathConfig pathConfig = createPathConfig(resourceDescription); paths.add(createPathConfig(resourceDescription));
paths.put(pathConfig.getPath(), pathConfig);
} }
} }
return paths; return paths;
} }
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) { private PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
PathConfig pathConfig = new PathConfig(); PathConfig pathConfig = new PathConfig();
pathConfig.setId(resourceDescription.getId()); pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName()); pathConfig.setName(resourceDescription.getName());
pathConfig.setPath(resourceDescription.getUri());
String uri = resourceDescription.getUri();
if (uri == null || "".equals(uri.trim())) {
throw new RuntimeException("Failed to configure paths. Resource [" + resourceDescription.getName() + "] has an invalid or empty URI [" + uri + "].");
}
pathConfig.setPath(uri);
List<String> scopeNames = new ArrayList<>(); List<String> scopeNames = new ArrayList<>();
@@ -247,8 +202,4 @@ public class PolicyEnforcer {
return pathConfig; return pathConfig;
} }
public PathMatcher getPathMatcher() {
return pathMatcher;
}
} }
@@ -17,17 +17,14 @@
package org.keycloak.adapters.jaas; package org.keycloak.adapters.jaas;
import org.jboss.logging.Logger; import java.io.InputStream;
import org.keycloak.KeycloakPrincipal; import java.lang.reflect.Constructor;
import org.keycloak.adapters.AdapterUtils; import java.security.Principal;
import org.keycloak.adapters.KeycloakDeployment; import java.util.HashSet;
import org.keycloak.adapters.KeycloakDeploymentBuilder; import java.util.Map;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import java.util.Set;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier; import java.util.concurrent.ConcurrentHashMap;
import org.keycloak.common.VerificationException; import java.util.concurrent.ConcurrentMap;
import org.keycloak.common.util.FindFile;
import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.representations.AccessToken;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.callback.Callback; import javax.security.auth.callback.Callback;
@@ -37,14 +34,19 @@ import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException; import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule; import javax.security.auth.spi.LoginModule;
import java.io.InputStream;
import java.lang.reflect.Constructor; import org.jboss.logging.Logger;
import java.security.Principal; import org.keycloak.KeycloakPrincipal;
import java.util.HashSet; import org.keycloak.RSATokenVerifier;
import java.util.Map; import org.keycloak.adapters.AdapterDeploymentContext;
import java.util.Set; import org.keycloak.adapters.AdapterUtils;
import java.util.concurrent.ConcurrentHashMap; import org.keycloak.adapters.KeycloakDeployment;
import java.util.concurrent.ConcurrentMap; import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.FindFile;
import org.keycloak.representations.AccessToken;
import org.keycloak.common.util.reflections.Reflections;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -86,6 +88,9 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
try { try {
InputStream is = FindFile.findFile(keycloakConfigFile); InputStream is = FindFile.findFile(keycloakConfigFile);
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is); KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
if (kd.getRealmKey() == null) {
new AdapterDeploymentContext().resolveRealmKey(kd);
}
return kd; return kd;
} catch (RuntimeException e) { } catch (RuntimeException e) {
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e); getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
@@ -185,7 +190,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
protected Auth bearerAuth(String tokenString) throws VerificationException { protected Auth bearerAuth(String tokenString) throws VerificationException {
AccessToken token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
boolean verifyCaller; boolean verifyCaller;
if (deployment.isUseResourceRoleMappings()) { if (deployment.isUseResourceRoleMappings()) {
@@ -17,6 +17,19 @@
package org.keycloak.adapters.jaas; package org.keycloak.adapters.jaas;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
@@ -26,25 +39,12 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/** /**
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which * Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
@@ -102,9 +102,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status); StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
if (entity != null) { if (entity != null) {
InputStream is = entity.getContent(); InputStream is = entity.getContent();
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class); Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError()) errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
.append(", Error description: " + errorRep.getErrorDescription()); .append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
} }
String error = errorBuilder.toString(); String error = errorBuilder.toString();
log.warn(error); log.warn(error);
@@ -161,9 +161,9 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
if (entity != null) { if (entity != null) {
InputStream is = entity.getContent(); InputStream is = entity.getContent();
if (status == 400) { if (status == 400) {
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class); Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError()) errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
.append(", Error description: " + errorRep.getErrorDescription()); .append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
} else { } else {
if (is != null) is.close(); if (is != null) is.close();
@@ -1,58 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.rotation;
import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.AccessToken;
import java.security.PublicKey;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AdapterRSATokenVerifier {
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
return verifyToken(tokenString, deployment, true, true);
}
public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
if (publicKey == null) {
log.errorf("Didn't find publicKey for kid: %s", kid);
throw new VerificationException("Didn't find publicKey for specified kid");
}
return publicKey;
}
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
return verifier.publicKey(publicKey).verify().getToken();
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.rotation;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jws.JWSInput;
import java.security.PublicKey;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class HardcodedPublicKeyLocator implements PublicKeyLocator {
private PublicKey publicKey;
public HardcodedPublicKeyLocator(PublicKey publicKey) {
this.publicKey = publicKey;
}
@Override
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
return publicKey;
}
@Override
public void reset(KeycloakDeployment deployment) {
}
}
@@ -1,115 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.rotation;
import org.apache.http.client.methods.HttpGet;
import org.jboss.logging.Logger;
import org.keycloak.adapters.HttpAdapterUtils;
import org.keycloak.adapters.HttpClientAdapterException;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jwk.JSONWebKeySet;
import org.keycloak.jose.jwk.JWK;
import org.keycloak.util.JWKSUtils;
import java.security.PublicKey;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* When needed, publicKeys are downloaded by sending request to realm's jwks_url
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWKPublicKeyLocator implements PublicKeyLocator {
private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
private volatile int lastRequestTime = 0;
@Override
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
int currentTime = Time.currentTime();
// Check if key is in cache.
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
if (publicKey != null) {
return publicKey;
}
// Check if we are allowed to send request
synchronized (this) {
currentTime = Time.currentTime();
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
sendRequest(deployment);
lastRequestTime = currentTime;
} else {
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime);
}
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
}
}
@Override
public void reset(KeycloakDeployment deployment) {
synchronized (this) {
sendRequest(deployment);
lastRequestTime = Time.currentTime();
}
}
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
return currentKeys.get(kid);
} else {
return null;
}
}
private void sendRequest(KeycloakDeployment deployment) {
if (log.isTraceEnabled()) {
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName());
}
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
try {
JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
if (log.isDebugEnabled()) {
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString());
}
// Update current keys
currentKeys.clear();
currentKeys.putAll(publicKeys);
} catch (HttpClientAdapterException e) {
log.error("Error when sending request to retrieve realm keys", e);
}
}
}
@@ -1,44 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.rotation;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jws.JWSInput;
import java.security.PublicKey;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface PublicKeyLocator {
/**
* @param kid
* @param deployment
* @return publicKey, which should be used for verify signature on given "input"
*/
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
/**
* Reset the state of locator (eg. clear the cached keys)
*
* @param deployment
*/
void reset(KeycloakDeployment deployment);
}
@@ -21,21 +21,16 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test; import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider; import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider; import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.enums.RelativeUrlsUsed; import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired; import org.keycloak.common.enums.SslRequired;
import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.common.util.PemUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
*/ */
public class KeycloakDeploymentBuilderTest { public class KeycloakDeploymentBuilderTest {
@@ -44,11 +39,7 @@ public class KeycloakDeploymentBuilderTest {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json")); KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
assertEquals("demo", deployment.getRealm()); assertEquals("demo", deployment.getRealm());
assertEquals("customer-portal", deployment.getResourceName()); assertEquals("customer-portal", deployment.getResourceName());
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString()); assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired()); assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
assertTrue(deployment.isUseResourceRoleMappings()); assertTrue(deployment.isUseResourceRoleMappings());
@@ -56,12 +47,10 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge()); assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods()); assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders()); assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly()); assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient()); assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth()); assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken()); assertTrue(deployment.isExposeToken());
assertFalse(deployment.isOAuthQueryParameterEnabled());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret")); assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId()); assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal()); assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
@@ -73,19 +62,12 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(TokenStore.COOKIE, deployment.getTokenStore()); assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute()); assertEquals("email", deployment.getPrincipalAttribute());
assertEquals(10, deployment.getTokenMinimumTimeToLive()); assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
} }
@Test @Test
public void loadNoClientCredentials() throws Exception { public void loadNoClientCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json")); KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId()); assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
assertEquals(86400, deployment.getPublicKeyCacheTtl());
} }
@Test @Test
@@ -1,49 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
*/
public class KeycloakDeploymentTest {
@Test
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
@Test
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
}
}
@@ -1,9 +1,9 @@
package org.keycloak.adapters; package org.keycloak.adapters;
import static org.junit.Assert.*;
import org.junit.Test; import org.junit.Test;
import org.keycloak.representations.oidc.TokenMetadataRepresentation; import org.keycloak.representations.oidc.TokenMetadataRepresentation;
import static org.junit.Assert.assertFalse;
/** /**
* @author github.com/tubbynl * @author github.com/tubbynl
* *
@@ -1,6 +1,7 @@
{ {
"realm": "demo", "realm": "demo",
"resource": "customer-portal", "resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth", "auth-server-url": "https://localhost:8443/auth",
"public-client": true, "public-client": true,
"expose-token": true "expose-token": true
@@ -9,7 +9,6 @@
"cors-max-age": 1000, "cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET", "cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2", "cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true, "bearer-only": true,
"public-client": true, "public-client": true,
"enable-basic-auth": true, "enable-basic-auth": true,
@@ -30,11 +29,5 @@
"register-node-period": 1000, "register-node-period": 1000,
"token-store": "cookie", "token-store": "cookie",
"principal-attribute": "email", "principal-attribute": "email",
"token-minimum-time-to-live": 10, "token-minimum-time-to-live": 10
"min-time-between-jwks-requests": 20,
"public-key-cache-ttl": 120,
"ignore-oauth-query-parameter": true,
"redirect-rewrite-rules" : {
"^/wsmaster/api/(.*)$" : "/api/$1"
}
} }
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -22,11 +22,15 @@ import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response; 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.apache.catalina.realm.GenericPrincipal;
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve; import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
import org.keycloak.adapters.tomcat.GenericPrincipalFactory; import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.security.Principal;
import java.util.List;
/** /**
* Keycloak authentication valve * Keycloak authentication valve
+3 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
@@ -88,6 +88,7 @@
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -103,6 +104,7 @@ projects that depend on this project.-->
<dependency> <dependency>
<groupId>org.jboss.msc</groupId> <groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId> <artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -49,6 +49,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
@Override @Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
String deploymentName = deploymentUnit.getName();
// if it's not a web-app there's nothing to secure // if it's not a web-app there's nothing to secure
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
@@ -66,25 +67,24 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP
// otherwise // otherwise
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig(); LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentUnit); boolean hasSubsystemConfig = service.isSecureDeployment(deploymentName);
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod()); boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
boolean isConfigured = service.isDeploymentConfigured(deploymentUnit);
if ((hasSubsystemConfig && isConfigured) || webRequiresKC) { if (hasSubsystemConfig || webRequiresKC) {
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentUnit.getName()); log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentName);
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it // if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
if (hasSubsystemConfig) { if (hasSubsystemConfig) {
addJSONData(service.getJSON(deploymentUnit), warMetaData); addJSONData(service.getJSON(deploymentName), warMetaData);
if (loginConfig != null) { if (loginConfig != null) {
loginConfig.setAuthMethod("KEYCLOAK"); loginConfig.setAuthMethod("KEYCLOAK");
loginConfig.setRealmName(service.getRealmName(deploymentUnit)); loginConfig.setRealmName(service.getRealmName(deploymentName));
} else { } else {
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)"); log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentName + " (loginConfig == null)");
} }
} }
addValve(webMetaData); addValve(webMetaData);
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName()); KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
} }
} }
@@ -17,12 +17,9 @@
package org.keycloak.subsystem.as7; package org.keycloak.subsystem.as7;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.web.deployment.WarMetaData;
import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property; import org.jboss.dmr.Property;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -157,23 +154,14 @@ public final class KeycloakAdapterConfigService {
return null; return null;
} }
public String getRealmName(DeploymentUnit deploymentUnit) { public String getRealmName(String deploymentName) {
ModelNode deployment = getSecureDeployment(deploymentUnit); ModelNode deployment = this.secureDeployments.get(deploymentName);
return deployment.get(RealmDefinition.TAG_NAME).asString(); return deployment.get(RealmDefinition.TAG_NAME).asString();
} }
protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) { public String getJSON(String deploymentName) {
ModelNode deployment = getSecureDeployment(deploymentUnit); ModelNode deployment = this.secureDeployments.get(deploymentName);
if (! deployment.isDefined()) {
return false;
}
ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName());
return resource.isDefined();
}
public String getJSON(DeploymentUnit deploymentUnit) {
ModelNode deployment = getSecureDeployment(deploymentUnit);
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString(); String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
ModelNode realm = this.realms.get(realmName); ModelNode realm = this.realms.get(realmName);
@@ -196,36 +184,9 @@ public final class KeycloakAdapterConfigService {
} }
} }
public boolean isSecureDeployment(DeploymentUnit deploymentUnit) { public boolean isSecureDeployment(String deploymentName) {
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size()); //log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
String deploymentName = preferredDeploymentName(deploymentUnit);
return this.secureDeployments.containsKey(deploymentName); return this.secureDeployments.containsKey(deploymentName);
} }
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
String deploymentName = preferredDeploymentName(deploymentUnit);
return this.secureDeployments.containsKey(deploymentName)
? this.secureDeployments.get(deploymentName)
: new ModelNode();
}
// KEYCLOAK-3273: prefer module name if available
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
String deploymentName = deploymentUnit.getName();
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) {
return deploymentName;
}
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return deploymentName;
}
String moduleName = webMetaData.getModuleName();
if (moduleName != null) return moduleName + ".war";
return deploymentName;
}
} }
@@ -46,7 +46,8 @@ public abstract class KeycloakDependencyProcessor implements DeploymentUnitProce
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) { String deploymentName = deploymentUnit.getName();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) { if (warMetaData == null) {
return; return;
@@ -25,6 +25,7 @@ import org.jboss.as.server.AbstractDeploymentChainStep;
import org.jboss.as.server.DeploymentProcessorTarget; import org.jboss.as.server.DeploymentProcessorTarget;
import org.jboss.as.server.deployment.Phase; import org.jboss.as.server.deployment.Phase;
import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceController;
import java.util.List; import java.util.List;
@@ -86,7 +86,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) { if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
//TODO: externalize the message //TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false."); throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
} }
list.add(addRealm); list.add(addRealm);
@@ -117,7 +117,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
* TODO need to check realm-ref first. * TODO need to check realm-ref first.
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) { if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
//TODO: externalize the message //TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false."); throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
} }
*/ */
@@ -157,7 +157,7 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<Li
credentialsToAdd.add(addCredential); credentialsToAdd.add(addCredential);
} else { } else {
for (Map.Entry<String, String> entry : values.entrySet()) { for (Map.Entry<String, String> entry : values.entrySet()) {
ModelNode addCredential = getCredentialToAdd(parent, name + "." + entry.getKey(), entry.getValue()); ModelNode addCredential = getCredentialToAdd(parent, name + "" + entry.getKey(), entry.getValue());
credentialsToAdd.add(addCredential); credentialsToAdd.add(addCredential);
} }
} }
@@ -46,7 +46,7 @@ public final class RealmAddHandler extends AbstractAddStepHandler {
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) { if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
//TODO: externalize message //TODO: externalize message
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false."); throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
} }
} }
@@ -23,6 +23,8 @@ import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelNode;
import java.util.List;
/** /**
* Update an attribute on a realm. * Update an attribute on a realm.
* *
@@ -23,7 +23,6 @@ import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.IntRangeValidator;
import org.jboss.as.controller.operations.validation.StringLengthValidator; import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.OperationEntry;
@@ -80,24 +79,6 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
.setAllowExpression(true) .setAllowExpression(true)
.setDefaultValue(new ModelNode(false)) .setDefaultValue(new ModelNode(false))
.build(); .build();
protected static final SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
.setXmlName("turn-off-change-session-id-on-login")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
.setXmlName("token-minimum-time-to-live")
.setValidator(new IntRangeValidator(-1, true))
.setAllowExpression(true)
.build();
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
.setXmlName("min-time-between-jwks-requests")
.setValidator(new IntRangeValidator(-1, true))
.setAllowExpression(true)
.build();
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>(); protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static { static {
@@ -107,9 +88,6 @@ class SecureDeploymentDefinition extends SimpleResourceDefinition {
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY); DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH); DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT); DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
} }
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>(); protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
@@ -80,8 +80,6 @@ keycloak.secure-deployment.register-node-period=how often to re-register node
keycloak.secure-deployment.token-store=cookie or session storage for auth session data keycloak.secure-deployment.token-store=cookie or session storage for auth session data
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
keycloak.secure-deployment.credential=Credential value keycloak.secure-deployment.credential=Credential value
@@ -105,8 +105,6 @@
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/> <xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" /> <xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
</xs:all> </xs:all>
<xs:attribute name="name" type="xs:string" use="required"> <xs:attribute name="name" type="xs:string" use="required">
<xs:annotation> <xs:annotation>
+1 -13
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name> <name>Keycloak AS7 / JBoss EAP 6 Integration</name>
@@ -30,18 +30,6 @@
<artifactId>keycloak-as7-integration-pom</artifactId> <artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules> <modules>
<module>as7-adapter-spi</module> <module>as7-adapter-spi</module>
<module>as7-adapter</module> <module>as7-adapter</module>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -19,11 +19,11 @@ package org.keycloak.adapters.installed;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException; import org.keycloak.OAuthErrorException;
import org.keycloak.RSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest; import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
@@ -119,7 +119,6 @@ public class KeycloakInstalled {
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
.build().toString(); .build().toString();
Desktop.getDesktop().browse(new URI(authUrl)); Desktop.getDesktop().browse(new URI(authUrl));
@@ -176,7 +175,6 @@ public class KeycloakInstalled {
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
.build().toString(); .build().toString();
printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>"); printer.println("Open the following URL in a browser. After login copy/paste the code back and press <enter>");
@@ -213,7 +211,7 @@ public class KeycloakInstalled {
refreshToken = tokenResponse.getRefreshToken(); refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken(); idTokenString = tokenResponse.getIdToken();
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment); token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) { if (idTokenString != null) {
try { try {
JWSInput input = new JWSInput(idTokenString); JWSInput input = new JWSInput(idTokenString);
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -20,6 +20,8 @@ package org.keycloak.jaxrs;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler; import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.BasicAuthRequestAuthenticator; import org.keycloak.adapters.BasicAuthRequestAuthenticator;
import org.keycloak.adapters.BearerTokenRequestAuthenticator; import org.keycloak.adapters.BearerTokenRequestAuthenticator;
@@ -29,8 +31,6 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement; import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler; import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.UserSessionManagement; import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.common.constants.GenericConstants; import org.keycloak.common.constants.GenericConstants;
@@ -40,6 +40,7 @@ import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching; import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.SecurityContext;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@@ -17,20 +17,21 @@
package org.keycloak.jaxrs; package org.keycloak.jaxrs;
import org.keycloak.KeycloakSecurityContext; import java.io.InputStream;
import org.keycloak.adapters.OIDCHttpFacade; import java.io.OutputStream;
import org.keycloak.adapters.spi.AuthenticationError; import java.util.List;
import org.keycloak.adapters.spi.LogoutError; import java.util.Map;
import org.keycloak.common.util.HostUtils;
import javax.security.cert.X509Certificate; import javax.security.cert.X509Certificate;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.SecurityContext;
import java.io.InputStream;
import java.io.OutputStream; import org.keycloak.KeycloakSecurityContext;
import java.util.List; import org.keycloak.adapters.OIDCHttpFacade;
import java.util.Map; import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.HostUtils;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -66,11 +67,6 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
return requestContext.getUriInfo().getRequestUri().toString(); return requestContext.getUriInfo().getRequestUri().toString();
} }
@Override
public String getRelativePath() {
return requestContext.getUriInfo().getPath();
}
@Override @Override
public boolean isSecure() { public boolean isSecure() {
return securityContext.isSecure(); return securityContext.isSecure();
@@ -20,7 +20,6 @@ package org.keycloak.jaxrs;
import org.keycloak.AbstractOAuthClient; import org.keycloak.AbstractOAuthClient;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.TokenUtil;
import javax.ws.rs.BadRequestException; import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.InternalServerErrorException;
@@ -86,13 +85,14 @@ public class JaxrsOAuthClient extends AbstractOAuthClient {
} }
public Response redirect(UriInfo uriInfo, String redirectUri) { public Response redirect(UriInfo uriInfo, String redirectUri) {
String state = getStateCode(); String state = getStateCode();
String scopeParam = TokenUtil.attachOIDCScope(scope);
UriBuilder uriBuilder = UriBuilder.fromUri(authUrl) UriBuilder uriBuilder = UriBuilder.fromUri(authUrl)
.queryParam(OAuth2Constants.CLIENT_ID, clientId) .queryParam(OAuth2Constants.CLIENT_ID, clientId)
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam(OAuth2Constants.STATE, state) .queryParam(OAuth2Constants.STATE, state);
.queryParam(OAuth2Constants.SCOPE, scopeParam); if (scope != null) {
uriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
}
URI url = uriBuilder.build(); URI url = uriBuilder.build();
@@ -17,18 +17,19 @@
package org.keycloak.jaxrs; package org.keycloak.jaxrs;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.common.constants.GenericConstants;
import org.osgi.framework.BundleContext;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.PreMatching;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.PreMatching;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.common.constants.GenericConstants;
import org.osgi.framework.BundleContext;
/** /**
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle * Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
* *
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -27,26 +27,28 @@ import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.security.authentication.LoginAuthenticator; import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext; import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler; import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakConfigResolver; import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder; import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement; import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler; import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.constants.AdapterConstants; import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore; import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.adapters.config.AdapterConfig;
@@ -59,6 +61,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -106,8 +109,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment); public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
public abstract JettyUserSessionManagement createSessionManagement(Request request);
public void logoutCurrent(Request request) { public void logoutCurrent(Request request) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName()); AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
@@ -289,7 +290,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
log.debug("*** deployment isn't configured return false"); log.debug("*** deployment isn't configured return false");
return Authentication.UNAUTHENTICATED; return Authentication.UNAUTHENTICATED;
} }
PreAuthActionsHandler handler = new PreAuthActionsHandler(createSessionManagement(request), deploymentContext, facade); PreAuthActionsHandler handler = new PreAuthActionsHandler(new JettyUserSessionManagement(request.getSessionManager()), deploymentContext, facade);
if (handler.handleRequest()) { if (handler.handleRequest()) {
return Authentication.SEND_SUCCESS; return Authentication.SEND_SUCCESS;
} }
@@ -339,13 +340,13 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
Authentication authentication = request.getAuthentication(); Authentication authentication = request.getAuthentication();
if (!(authentication instanceof KeycloakAuthentication)) { if (!(authentication instanceof KeycloakAuthentication)) {
UserIdentity userIdentity = createIdentity(principal); UserIdentity userIdentity = createIdentity(principal);
authentication = createAuthentication(userIdentity, request); authentication = createAuthentication(userIdentity);
request.setAuthentication(authentication); request.setAuthentication(authentication);
} }
return authentication; return authentication;
} }
protected abstract Authentication createAuthentication(UserIdentity userIdentity, Request request); protected abstract Authentication createAuthentication(UserIdentity userIdentity);
public static abstract class KeycloakAuthentication extends UserAuthentication { public static abstract class KeycloakAuthentication extends UserAuthentication {
public KeycloakAuthentication(String method, UserIdentity userIdentity) { public KeycloakAuthentication(String method, UserIdentity userIdentity) {
@@ -23,11 +23,11 @@ import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.CookieTokenStore; import org.keycloak.adapters.CookieTokenStore;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator; import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
/** /**
* Handle storage of token info in cookie. Per-request object. * Handle storage of token info in cookie. Per-request object.
@@ -23,12 +23,12 @@ import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator; import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator; import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.security.Principal; import java.security.Principal;
@@ -21,13 +21,13 @@ import org.eclipse.jetty.server.Request;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext; import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.AdapterTokenStore; import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils; import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount; import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext; import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator; import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.AdapterSessionStore;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty81SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty81SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -20,8 +20,8 @@ package org.keycloak.adapters.jetty;
import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore; import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@@ -25,7 +25,6 @@ import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator; import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -45,18 +44,13 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty81SessionManager(request.getSessionManager()));
}
@Override @Override
protected Request resolveRequest(ServletRequest req) { protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest(); return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest();
} }
@Override @Override
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) { protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) { return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override @Override
public void logout() { public void logout() {
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty91SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty91SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore; import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@@ -27,7 +27,6 @@ import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade; import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -47,18 +46,13 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty91SessionManager(request.getSessionManager()));
}
@Override @Override
protected Request resolveRequest(ServletRequest req) { protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest(); return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
} }
@Override @Override
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) { protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) { return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override @Override
public void logout() { public void logout() {
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../../pom.xml</relativePath> <relativePath>../../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -1,40 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty92SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty92SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -21,8 +21,8 @@ import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.authentication.FormAuthenticator; import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore; import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
@@ -27,7 +27,6 @@ import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator; import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore; import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade; import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
@@ -48,7 +47,7 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
} }
@Override @Override
protected Authentication createAuthentication(UserIdentity userIdentity, Request request) { protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) { return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override @Override
public void logout() { public void logout() {
@@ -62,11 +61,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request)); return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
} }
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty92SessionManager(request.getSessionManager()));
}
@Override @Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade, protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) { KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
-156
View File
@@ -1,156 +0,0 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty93-adapter</artifactId>
<name>Keycloak Jetty 9.3.x Integration</name>
<properties>
<jetty9.version>9.3.9.v20160517</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;resolution:=optional,
javax.servlet.*;version="[3.0,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Request;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty93RequestAuthenticator extends JettyRequestAuthenticator {
public Jetty93RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort, request);
}
@Override
protected String changeHttpSessionId(boolean create) {
Request request = this.request;
HttpSession session = request.getSession(false);
if (session == null) {
return request.getSession(true).getId();
}
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}
@@ -1,40 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.SessionManager;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty93SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty93SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
MultiMap<String> formParameters = new MultiMap<String>();
base_request.extractFormParameters(formParameters);
return formParameters;
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setContentParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
protected Request resolveRequest(ServletRequest req) {
return Request.getBaseRequest(req);
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity, final Request request) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(request);
}
};
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty93SessionManager(request.getSessionManager()));
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new Jetty93RequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
}
-156
View File
@@ -1,156 +0,0 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>3.2.0.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty94-adapter</artifactId>
<name>Keycloak Jetty 9.4.x Integration</name>
<properties>
<jetty9.version>9.4.2.v20170220</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;resolution:=optional,
javax.servlet.*;version="[3.0,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Request;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty94RequestAuthenticator extends JettyRequestAuthenticator {
public Jetty94RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort, request);
}
@Override
protected String changeHttpSessionId(boolean create) {
Request request = this.request;
HttpSession session = request.getSession(false);
if (session == null) {
return request.getSession(true).getId();
}
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}
@@ -1,40 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.session.SessionHandler;
import org.keycloak.adapters.jetty.spi.JettySessionManager;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty94SessionManager implements JettySessionManager {
protected SessionHandler sessionHandler;
public Jetty94SessionManager(SessionHandler sessionHandler) {
this.sessionHandler = sessionHandler;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionHandler.getHttpSession(id);
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
MultiMap<String> formParameters = new MultiMap<String>();
base_request.extractFormParameters(formParameters);
return formParameters;
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setContentParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,75 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
protected Request resolveRequest(ServletRequest req) {
return Request.getBaseRequest(req);
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity, final Request request) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(request);
}
};
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty94SessionManager(request.getSessionHandler()));
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new Jetty94RequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
}
+2 -17
View File
@@ -20,7 +20,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<name>Keycloak Jetty Integration</name> <name>Keycloak Jetty Integration</name>
@@ -34,21 +34,6 @@
<module>jetty-core</module> <module>jetty-core</module>
<module>jetty8.1</module> <module>jetty8.1</module>
<module>jetty9.2</module> <module>jetty9.2</module>
<module>jetty9.3</module> <module>jetty9.1</module>
</modules> </modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty9.1</module>
<module>jetty9.4</module>
</modules>
</profile>
</profiles>
</project> </project>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent> <parent>
<artifactId>keycloak-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>3.2.0.Final</version> <version>2.0.0.Test2</version>
<relativePath>../../../pom.xml</relativePath> <relativePath>../../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
-59
View File
@@ -1,59 +0,0 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import * as Keycloak from 'keycloak';
export as namespace KeycloakAuthorization;
export = KeycloakAuthorization;
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
declare namespace KeycloakAuthorization {
interface KeycloakAuthorizationPromise {
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
}
interface KeycloakAuthorizationInstance {
rpt: any;
config: { rpt_endpoint: string };
init(): void;
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer.
*
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
* depending on how the policy enforcer at the resource server was configured.
*/
authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
/**
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
*/
entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
}
}
@@ -17,7 +17,7 @@
*/ */
(function( window, undefined ) { (function( window, undefined ) {
var KeycloakAuthorization = function (keycloak) { var KeycloakAuthorization = function (keycloak) {
var _instance = this; var _instance = this;
this.rpt = null; this.rpt = null;
@@ -49,7 +49,7 @@
*/ */
this.authorize = function (wwwAuthenticateHeader) { this.authorize = function (wwwAuthenticateHeader) {
this.then = function (onGrant, onDeny, onError) { this.then = function (onGrant, onDeny, onError) {
if (wwwAuthenticateHeader.indexOf('UMA') != -1) { if (wwwAuthenticateHeader.startsWith('UMA')) {
var params = wwwAuthenticateHeader.split(','); var params = wwwAuthenticateHeader.split(',');
for (i = 0; i < params.length; i++) { for (i = 0; i < params.length; i++) {
@@ -96,7 +96,7 @@
)); ));
} }
} }
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) { } else if (wwwAuthenticateHeader.startsWith('KC_ETT')) {
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(','); var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
var clientId = null; var clientId = null;
@@ -112,55 +112,44 @@
} }
}; };
return this; /**
}; * Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
*/
this.entitlement = function (resourceSeververId) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
/** request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId. request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
*/
this.entitlement = function (resourceSeververId, entitlementRequest ) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
request.onreadystatechange = function () { var rpt = JSON.parse(request.responseText).rpt;
if (request.readyState == 4) { _instance.rpt = rpt;
var status = request.status; onGrant(rpt);
} else if (status == 403) {
if (status >= 200 && status < 300) { if (onDeny) {
var rpt = JSON.parse(request.responseText).rpt; onDeny();
_instance.rpt = rpt; } else {
onGrant(rpt); console.error('Authorization request was denied by the server.');
} else if (status == 403) { }
if (onDeny) {
onDeny();
} else { } else {
console.error('Authorization request was denied by the server.'); if (onError) {
} onError();
} else { } else {
if (onError) { console.error('Could not obtain authorization data from server.');
onError(); }
} else {
console.error('Could not obtain authorization data from server.');
} }
} }
} };
request.send(null);
}; };
var erJson = null return this;
if(entitlementRequest) {
request.open('POST', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
request.setRequestHeader("Content-type", "application/json");
erJson = JSON.stringify(entitlementRequest)
} else {
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/authz/entitlement/' + resourceSeververId, true);
}
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.send(erJson);
}; };
return this; return this;
-478
View File
@@ -1,478 +0,0 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export as namespace Keycloak;
export = Keycloak;
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
declare namespace Keycloak {
type KeycloakAdapterName = 'cordova'|'default';
type KeycloakOnLoad = 'login-required'|'check-sso';
type KeycloakResponseMode = 'query'|'fragment';
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
interface KeycloakInitOptions {
/**
* @private Undocumented.
*/
adapter?: KeycloakAdapterName;
/**
* Specifies an action to do on load.
*/
onLoad?: KeycloakOnLoad;
/**
* Set an initial value for the token.
*/
token?: string;
/**
* Set an initial value for the refresh token.
*/
refreshToken?: string;
/**
* Set an initial value for the id token (only together with `token` or
* `refreshToken`).
*/
idToken?: string;
/**
* Set an initial value for skew between local time and Keycloak server in
* seconds (only together with `token` or `refreshToken`).
*/
timeSkew?: number;
/**
* Set to enable/disable monitoring login state.
* @default true
*/
checkLoginIframe?: boolean;
/**
* Set the interval to check login state (in seconds).
* @default 5
*/
checkLoginIframeInterval?: boolean;
/**
* Set the OpenID Connect response mode to send to Keycloak upon login.
* @default fragment After successful authentication Keycloak will redirect
* to JavaScript application with OpenID Connect parameters
* added in URL fragment. This is generally safer and
* recommended over query.
*/
responseMode?: KeycloakResponseMode;
/**
* Set the OpenID Connect flow.
* @default standard
*/
flow?: KeycloakFlow;
}
interface KeycloakLoginOptions {
/**
* @private Undocumented.
*/
scope?: string;
/**
* Specifies the uri to redirect to after login.
*/
redirectUri?: string;
/**
* By default the login screen is displayed if the user is not logged into
* Keycloak. To only authenticate to the application if the user is already
* logged in and not display the login page if the user is not logged in, set
* this option to `'none'`. To always require re-authentication and ignore
* SSO, set this option to `'login'`.
*/
prompt?: 'none'|'login';
/**
* If value is `'register'` then user is redirected to registration page,
* otherwise to login page.
*/
action?: 'register';
/**
* Used just if user is already authenticated. Specifies maximum time since
* the authentication of user happened. If user is already authenticated for
* longer time than `'maxAge'`, the SSO is ignored and he will need to
* authenticate again.
*/
maxAge?: number;
/**
* Used to pre-fill the username/email field on the login form.
*/
loginHint?: string;
/**
* Used to tell Keycloak which IDP the user wants to authenticate with.
*/
idpHint?: string;
/**
* Specifies the desired locale for the UI.
*/
locale?: string;
}
type KeycloakPromiseCallback<T> = (result: T) => void;
interface KeycloakPromise<TSuccess, TError> {
/**
* Function to call if the promised action succeeds.
*/
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
/**
* Function to call if the promised action throws an error.
*/
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
}
interface KeycloakError {
error: string;
error_description: string;
}
interface KeycloakAdapter {
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
logout(options?: any): KeycloakPromise<void, void>;
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
accountManagement(): KeycloakPromise<void, void>;
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
}
interface KeycloakProfile {
id?: string;
username?: string;
email?: string;
firstName?: string;
lastName?: string;
enabled?: boolean;
emailVerified?: boolean;
totp?: boolean;
createdTimestamp?: number;
}
// export interface KeycloakUserInfo {}
/**
* A client for the Keycloak authentication server.
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
*/
interface KeycloakInstance {
/**
* Is true if the user is authenticated, false otherwise.
*/
authenticated?: boolean;
/**
* The user id.
*/
subject?: string;
/**
* Response mode passed in init (default value is `'fragment'`).
*/
responseMode?: KeycloakResponseMode;
/**
* Response type sent to Keycloak with login requests. This is determined
* based on the flow value used during initialization, but can be overridden
* by setting this value.
*/
responseType?: KeycloakResponseType;
/**
* Flow passed in init.
*/
flow?: KeycloakFlow;
/**
* The realm roles associated with the token.
*/
realmAccess?: { roles: string[] };
/**
* The resource roles associated with the token.
*/
resourceAccess?: string[];
/**
* The base64 encoded token that can be sent in the Authorization header in
* requests to services.
*/
token?: string;
/**
* The parsed token as a JavaScript object.
*/
tokenParsed?: {
exp?: number;
iat?: number;
nonce?: string;
sub?: string;
session_state?: string;
realm_access?: { roles: string[] };
resource_access?: string[];
};
/**
* The base64 encoded refresh token that can be used to retrieve a new token.
*/
refreshToken?: string;
/**
* The parsed refresh token as a JavaScript object.
*/
refreshTokenParsed?: { nonce?: string };
/**
* The base64 encoded ID token.
*/
idToken?: string;
/**
* The parsed id token as a JavaScript object.
*/
idTokenParsed?: { nonce?: string };
/**
* The estimated time difference between the browser time and the Keycloak
* server in seconds. This value is just an estimation, but is accurate
* enough when determining if a token is expired or not.
*/
timeSkew?: number;
/**
* @private Undocumented.
*/
loginRequired?: boolean;
/**
* @private Undocumented.
*/
authServerUrl?: string;
/**
* @private Undocumented.
*/
realm?: string;
/**
* @private Undocumented.
*/
clientId?: string;
/**
* @private Undocumented.
*/
clientSecret?: string;
/**
* @private Undocumented.
*/
redirectUri?: string;
/**
* @private Undocumented.
*/
sessionId?: string;
/**
* @private Undocumented.
*/
profile?: KeycloakProfile;
/**
* @private Undocumented.
*/
userInfo?: {}; // KeycloakUserInfo;
/**
* Called when the adapter is initialized.
*/
onReady?(authenticated?: boolean): void;
/**
* Called when a user is successfully authenticated.
*/
onAuthSuccess?(): void;
/**
* Called if there was an error during authentication.
*/
onAuthError?(errorData: KeycloakError): void;
/**
* Called when the token is refreshed.
*/
onAuthRefreshSuccess?(): void;
/**
* Called if there was an error while trying to refresh the token.
*/
onAuthRefreshError?(): void;
/**
* Called if the user is logged out (will only be called if the session
* status iframe is enabled, or in Cordova mode).
*/
onAuthLogout?(): void;
/**
* Called when the access token is expired. If a refresh token is available
* the token can be refreshed with Keycloak#updateToken, or in cases where
* it's not (ie. with implicit flow) you can redirect to login screen to
* obtain a new access token.
*/
onTokenExpired?(): void;
/**
* Called to initialize the adapter.
* @param initOptions Initialization options.
* @returns A promise to set functions to be invoked on success or error.
*/
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
/**
* Redirects to login form.
* @param options Login options.
*/
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
/**
* Redirects to logout.
* @param options Logout options.
* @param options.redirectUri Specifies the uri to redirect to after logout.
*/
logout(options?: any): KeycloakPromise<void, void>;
/**
* Redirects to registration form.
* @param options Supports same options as Keycloak#login but `action` is
* set to `'register'`.
*/
register(options?: any): KeycloakPromise<void, void>;
/**
* Redirects to the Account Management Console.
*/
accountManagement(): KeycloakPromise<void, void>;
/**
* Returns the URL to login form.
* @param options Supports same options as Keycloak#login.
*/
createLoginUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to logout the user.
* @param options Logout options.
* @param options.redirectUri Specifies the uri to redirect to after logout.
*/
createLogoutUrl(options?: any): string;
/**
* Returns the URL to registration page.
* @param options Supports same options as Keycloak#createLoginUrl but
* `action` is set to `'register'`.
*/
createRegisterUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to the Account Management Console.
*/
createAccountUrl(): string;
/**
* Returns true if the token has less than `minValidity` seconds left before
* it expires.
* @param minValidity If not specified, `0` is used.
*/
isTokenExpired(minValidity?: number): boolean;
/**
* If the token expires within `minValidity` seconds, the token is refreshed.
* If the session status iframe is enabled, the session status is also
* checked.
* @returns A promise to set functions that can be invoked if the token is
* still valid, or if the token is no longer valid.
* @example
* ```js
* keycloak.updateToken(5).success(function(refreshed) {
* if (refreshed) {
* alert('Token was successfully refreshed');
* } else {
* alert('Token is still valid');
* }
* }).error(function() {
* alert('Failed to refresh the token, or the session has expired');
* });
*/
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
/**
* Clears authentication state, including tokens. This can be useful if
* the application has detected the session was expired, for example if
* updating token fails. Invoking this results in Keycloak#onAuthLogout
* callback listener being invoked.
*/
clearToken(): void;
/**
* Returns true if the token has the given realm role.
* @param role A realm role name.
*/
hasRealmRole(role: string): boolean;
/**
* Returns true if the token has the given role for the resource.
* @param role A role name.
* @param resource If not specified, `clientId` is used.
*/
hasResourceRole(role: string, resource?: string): boolean;
/**
* Loads the user's profile.
* @returns A promise to set functions to be invoked on success or error.
*/
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
/**
* @private Undocumented.
*/
loadUserInfo(): KeycloakPromise<{}, void>;
}
}
+130 -228
View File
@@ -25,21 +25,21 @@
var kc = this; var kc = this;
var adapter; var adapter;
var refreshQueue = []; var refreshQueue = [];
var callbackStorage; var storage;
var loginIframe = { var loginIframe = {
enable: true, enable: true,
callbackList: [], callbackMap: [],
interval: 5 interval: 5
}; };
kc.init = function (initOptions) { kc.init = function (initOptions) {
kc.authenticated = false; kc.authenticated = false;
callbackStorage = createCallbackStorage(); storage = new PersistentStorage();
if (initOptions && initOptions.adapter === 'cordova') { if (initOptions && initOptions.adapter === 'cordova') {
adapter = loadAdapter('cordova'); adapter = loadAdapter('cordova');
} else if (initOptions && initOptions.adapter === 'default') { } else if (initOptions && initOptions.adapter === 'default') {
adapter = loadAdapter(); adapter = loadAdapter();
} else { } else {
@@ -87,10 +87,6 @@
} }
kc.flow = initOptions.flow; kc.flow = initOptions.flow;
} }
if (initOptions.timeSkew != null) {
kc.timeSkew = initOptions.timeSkew;
}
} }
if (!kc.responseMode) { if (!kc.responseMode) {
@@ -107,8 +103,8 @@
initPromise.promise.success(function() { initPromise.promise.success(function() {
kc.onReady && kc.onReady(kc.authenticated); kc.onReady && kc.onReady(kc.authenticated);
promise.setSuccess(kc.authenticated); promise.setSuccess(kc.authenticated);
}).error(function(errorData) { }).error(function() {
promise.setError(errorData); promise.setError();
}); });
var configPromise = loadConfig(config); var configPromise = loadConfig(config);
@@ -157,31 +153,22 @@
processCallback(callback, initPromise); processCallback(callback, initPromise);
return; return;
} else if (initOptions) { } else if (initOptions) {
if (initOptions.token && initOptions.refreshToken) { if (initOptions.token || initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken, false);
kc.timeSkew = initOptions.timeSkew || 0;
if (loginIframe.enable) { if (loginIframe.enable) {
setupCheckLoginIframe().success(function() { setupCheckLoginIframe().success(function() {
checkLoginIframe().success(function () { checkLoginIframe().success(function () {
kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess(); initPromise.setSuccess();
}).error(function () { }).error(function () {
setToken(null, null, null); if (initOptions.onLoad) {
initPromise.setSuccess(); onLoad();
}
}); });
}); });
} else { } else {
kc.updateToken(-1).success(function() { initPromise.setSuccess();
kc.onAuthSuccess && kc.onAuthSuccess();
initPromise.setSuccess();
}).error(function() {
kc.onAuthError && kc.onAuthError();
if (initOptions.onLoad) {
onLoad();
} else {
initPromise.setError();
}
});
} }
} else if (initOptions.onLoad) { } else if (initOptions.onLoad) {
onLoad(); onLoad();
@@ -210,26 +197,17 @@
var nonce = createUUID(); var nonce = createUUID();
var redirectUri = adapter.redirectUri(options); var redirectUri = adapter.redirectUri(options);
var callbackState = {
state: state,
nonce: nonce,
redirectUri: encodeURIComponent(redirectUri),
}
if (options && options.prompt) { if (options && options.prompt) {
callbackState.prompt = options.prompt; redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
} }
callbackStorage.add(callbackState); storage.setItem('oauthState', JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) }));
var action = 'auth'; var action = 'auth';
if (options && options.action == 'register') { if (options && options.action == 'register') {
action = 'registrations'; action = 'registrations';
} }
var scope = (options && options.scope) ? "openid " + options.scope : "openid";
var url = getRealmUrl() var url = getRealmUrl()
+ '/protocol/openid-connect/' + action + '/protocol/openid-connect/' + action
+ '?client_id=' + encodeURIComponent(kc.clientId) + '?client_id=' + encodeURIComponent(kc.clientId)
@@ -237,17 +215,12 @@
+ '&state=' + encodeURIComponent(state) + '&state=' + encodeURIComponent(state)
+ '&nonce=' + encodeURIComponent(nonce) + '&nonce=' + encodeURIComponent(nonce)
+ '&response_mode=' + encodeURIComponent(kc.responseMode) + '&response_mode=' + encodeURIComponent(kc.responseMode)
+ '&response_type=' + encodeURIComponent(kc.responseType) + '&response_type=' + encodeURIComponent(kc.responseType);
+ '&scope=' + encodeURIComponent(scope);
if (options && options.prompt) { if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt); url += '&prompt=' + encodeURIComponent(options.prompt);
} }
if (options && options.maxAge) {
url += '&max_age=' + encodeURIComponent(options.maxAge);
}
if (options && options.loginHint) { if (options && options.loginHint) {
url += '&login_hint=' + encodeURIComponent(options.loginHint); url += '&login_hint=' + encodeURIComponent(options.loginHint);
} }
@@ -256,6 +229,10 @@
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
} }
if (options && options.scope) {
url += '&scope=' + encodeURIComponent(options.scope);
}
if (options && options.locale) { if (options && options.locale) {
url += '&ui_locales=' + encodeURIComponent(options.locale); url += '&ui_locales=' + encodeURIComponent(options.locale);
} }
@@ -369,22 +346,18 @@
throw 'Not authenticated'; throw 'Not authenticated';
} }
if (kc.timeSkew == null) { var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew;
console.info('[KEYCLOAK] Unable to determine if token is expired as timeskew is not set');
return true;
}
var expiresIn = kc.tokenParsed['exp'] - Math.ceil(new Date().getTime() / 1000) + kc.timeSkew;
if (minValidity) { if (minValidity) {
expiresIn -= minValidity; expiresIn -= minValidity;
} }
return expiresIn < 0; return expiresIn < 0;
} }
kc.updateToken = function(minValidity) { kc.updateToken = function(minValidity) {
var promise = createPromise(); var promise = createPromise();
if (!kc.refreshToken) { if (!kc.tokenParsed || !kc.refreshToken) {
promise.setError(); promise.setError();
return promise.promise; return promise.promise;
} }
@@ -392,16 +365,7 @@
minValidity = minValidity || 5; minValidity = minValidity || 5;
var exec = function() { var exec = function() {
var refreshToken = false; if (!kc.isTokenExpired(minValidity)) {
if (minValidity == -1) {
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: forced refresh');
} else if (!kc.tokenParsed || kc.isTokenExpired(minValidity)) {
refreshToken = true;
console.info('[KEYCLOAK] Refreshing token: token expired');
}
if (!refreshToken) {
promise.setSuccess(false); promise.setSuccess(false);
} else { } else {
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
@@ -413,7 +377,6 @@
var req = new XMLHttpRequest(); var req = new XMLHttpRequest();
req.open('POST', url, true); req.open('POST', url, true);
req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
req.withCredentials = true;
if (kc.clientId && kc.clientSecret) { if (kc.clientId && kc.clientSecret) {
req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret));
@@ -426,21 +389,18 @@
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200) { if (req.status == 200) {
console.info('[KEYCLOAK] Token refreshed');
timeLocal = (timeLocal + new Date().getTime()) / 2; timeLocal = (timeLocal + new Date().getTime()) / 2;
var tokenResponse = JSON.parse(req.responseText); var tokenResponse = JSON.parse(req.responseText);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], true);
setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token'], timeLocal); kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setSuccess(true); p.setSuccess(true);
} }
} else { } else {
console.warn('[KEYCLOAK] Failed to refresh token');
kc.onAuthRefreshError && kc.onAuthRefreshError(); kc.onAuthRefreshError && kc.onAuthRefreshError();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setError(true); p.setError(true);
@@ -470,7 +430,7 @@
kc.clearToken = function() { kc.clearToken = function() {
if (kc.token) { if (kc.token) {
setToken(null, null, null); setToken(null, null, null, true);
kc.onAuthLogout && kc.onAuthLogout(); kc.onAuthLogout && kc.onAuthLogout();
if (kc.loginRequired) { if (kc.loginRequired) {
kc.login(); kc.login();
@@ -503,9 +463,8 @@
if (error) { if (error) {
if (prompt != 'none') { if (prompt != 'none') {
var errorData = { error: error, error_description: oauth.error_description }; kc.onAuthError && kc.onAuthError();
kc.onAuthError && kc.onAuthError(errorData); promise && promise.setError();
promise && promise.setError(errorData);
} else { } else {
promise && promise.setSuccess(); promise && promise.setSuccess();
} }
@@ -551,16 +510,18 @@
function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) { function authSuccess(accessToken, refreshToken, idToken, fulfillPromise) {
timeLocal = (timeLocal + new Date().getTime()) / 2; timeLocal = (timeLocal + new Date().getTime()) / 2;
setToken(accessToken, refreshToken, idToken, timeLocal); setToken(accessToken, refreshToken, idToken, true);
if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) || if ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
(kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) || (kc.refreshTokenParsed && kc.refreshTokenParsed.nonce != oauth.storedNonce) ||
(kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) { (kc.idTokenParsed && kc.idTokenParsed.nonce != oauth.storedNonce)) {
console.info('[KEYCLOAK] Invalid nonce, clearing token'); console.log('invalid nonce!');
kc.clearToken(); kc.clearToken();
promise && promise.setError(); promise && promise.setError();
} else { } else {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
if (fulfillPromise) { if (fulfillPromise) {
kc.onAuthSuccess && kc.onAuthSuccess(); kc.onAuthSuccess && kc.onAuthSuccess();
promise && promise.setSuccess(); promise && promise.setSuccess();
@@ -587,7 +548,7 @@
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) { if (req.status == 200) {
var config = JSON.parse(req.responseText); var config = JSON.parse(req.responseText);
kc.authServerUrl = config['auth-server-url']; kc.authServerUrl = config['auth-server-url'];
@@ -633,16 +594,41 @@
return promise.promise; return promise.promise;
} }
function fileLoaded(xhr) { function setToken(token, refreshToken, idToken, useTokenTime) {
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
}
function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) { if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle); clearTimeout(kc.tokenTimeoutHandle);
kc.tokenTimeoutHandle = null; kc.tokenTimeoutHandle = null;
} }
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
var sessionId = kc.realm + '/' + kc.tokenParsed.sub;
if (kc.tokenParsed.session_state) {
sessionId = sessionId + '/' + kc.tokenParsed.session_state;
}
kc.sessionId = sessionId;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (kc.onTokenExpired) {
var start = useTokenTime ? kc.tokenParsed.iat : (new Date().getTime() / 1000);
var expiresIn = kc.tokenParsed.exp - start;
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn * 1000);
}
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
if (refreshToken) { if (refreshToken) {
kc.refreshToken = refreshToken; kc.refreshToken = refreshToken;
kc.refreshTokenParsed = decodeToken(refreshToken); kc.refreshTokenParsed = decodeToken(refreshToken);
@@ -658,42 +644,6 @@
delete kc.idToken; delete kc.idToken;
delete kc.idTokenParsed; delete kc.idTokenParsed;
} }
if (token) {
kc.token = token;
kc.tokenParsed = decodeToken(token);
kc.sessionId = kc.tokenParsed.session_state;
kc.authenticated = true;
kc.subject = kc.tokenParsed.sub;
kc.realmAccess = kc.tokenParsed.realm_access;
kc.resourceAccess = kc.tokenParsed.resource_access;
if (timeLocal) {
kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat;
}
if (kc.timeSkew != null) {
console.info('[KEYCLOAK] Estimated time difference between browser and server is ' + kc.timeSkew + ' seconds');
if (kc.onTokenExpired) {
var expiresIn = (kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew) * 1000;
console.info('[KEYCLOAK] Token expires in ' + Math.round(expiresIn / 1000) + ' s');
if (expiresIn <= 0) {
kc.onTokenExpired();
} else {
kc.tokenTimeoutHandle = setTimeout(kc.onTokenExpired, expiresIn);
}
}
}
} else {
delete kc.token;
delete kc.tokenParsed;
delete kc.subject;
delete kc.realmAccess;
delete kc.resourceAccess;
kc.authenticated = false;
}
} }
function decodeToken(str) { function decodeToken(str) {
@@ -747,12 +697,15 @@
function parseCallback(url) { function parseCallback(url) {
var oauth = new CallbackParser(url, kc.responseMode).parseUri(); var oauth = new CallbackParser(url, kc.responseMode).parseUri();
var oauthState = callbackStorage.get(oauth.state);
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) { var oauthState = storage.getItem('oauthState');
oauth.redirectUri = oauthState.redirectUri; var sessionState = oauthState && JSON.parse(oauthState);
oauth.storedNonce = oauthState.nonce;
oauth.prompt = oauthState.prompt; if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
storage.removeItem('oauthState');
oauth.redirectUri = sessionState.redirectUri;
oauth.storedNonce = sessionState.nonce;
if (oauth.fragment) { if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment; oauth.newUrl += '#' + oauth.fragment;
@@ -830,37 +783,26 @@
setTimeout(check, loginIframe.interval * 1000); setTimeout(check, loginIframe.interval * 1000);
} }
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html'; var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin();
iframe.setAttribute('src', src ); iframe.setAttribute('src', src );
iframe.style.display = 'none'; iframe.style.display = 'none';
document.body.appendChild(iframe); document.body.appendChild(iframe);
var messageCallback = function(event) { var messageCallback = function(event) {
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) { if (event.origin !== loginIframe.iframeOrigin) {
return; return;
} }
var data = JSON.parse(event.data);
var promise = loginIframe.callbackMap[data.callbackId];
delete loginIframe.callbackMap[data.callbackId];
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) { if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) {
return; promise.setSuccess();
} } else {
if (event.data != 'unchanged') {
kc.clearToken(); kc.clearToken();
} promise.setError();
var callbacks = loginIframe.callbackList.splice(0, loginIframe.callbackList.length);
for (var i = callbacks.length - 1; i >= 0; --i) {
var promise = callbacks[i];
if (event.data == 'unchanged') {
promise.setSuccess();
} else {
promise.setError();
}
} }
}; };
window.addEventListener('message', messageCallback, false); window.addEventListener('message', messageCallback, false);
var check = function() { var check = function() {
@@ -876,13 +818,12 @@
function checkLoginIframe() { function checkLoginIframe() {
var promise = createPromise(); var promise = createPromise();
if (loginIframe.iframe && loginIframe.iframeOrigin ) { if (loginIframe.iframe && loginIframe.iframeOrigin) {
var msg = kc.clientId + ' ' + kc.sessionId; var msg = {};
loginIframe.callbackList.push(promise); msg.callbackId = createCallbackId();
loginIframe.callbackMap[msg.callbackId] = promise;
var origin = loginIframe.iframeOrigin; var origin = loginIframe.iframeOrigin;
if (loginIframe.callbackList.length == 1) { loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin);
loginIframe.iframe.contentWindow.postMessage(msg, origin);
}
} else { } else {
promise.setSuccess(); promise.setSuccess();
} }
@@ -1041,93 +982,60 @@
throw 'invalid adapter type: ' + type; throw 'invalid adapter type: ' + type;
} }
var LocalStorage = function() {
if (!(this instanceof LocalStorage)) { var PersistentStorage = function() {
return new LocalStorage(); if (!(this instanceof PersistentStorage)) {
return new PersistentStorage();
} }
var ps = this;
localStorage.setItem('kc-test', 'test'); var useCookieStorage = function () {
localStorage.removeItem('kc-test'); if (typeof localStorage === "undefined") {
return true;
var cs = this;
function clearExpired() {
var time = new Date().getTime();
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key && key.indexOf('kc-callback-') == 0) {
var value = localStorage.getItem(key);
if (value) {
try {
var expires = JSON.parse(value).expires;
if (!expires || expires < time) {
localStorage.removeItem(key);
}
} catch (err) {
localStorage.removeItem(key);
}
}
}
} }
} try {
var key = '@@keycloak-session-storage/test';
cs.get = function(state) { localStorage.setItem(key, 'test');
if (!state) {
return;
}
var key = 'kc-callback-' + state;
var value = localStorage.getItem(key);
if (value) {
localStorage.removeItem(key); localStorage.removeItem(key);
value = JSON.parse(value); return false;
} catch (err) {
// Probably in Safari "private mode" where localStorage
// quota is 0, or quota exceeded. Switching to cookie
// storage.
return true;
} }
clearExpired();
return value;
};
cs.add = function(state) {
clearExpired();
var key = 'kc-callback-' + state.state;
state.expires = new Date().getTime() + (60 * 60 * 1000);
localStorage.setItem(key, JSON.stringify(state));
};
};
var CookieStorage = function() {
if (!(this instanceof CookieStorage)) {
return new CookieStorage();
} }
var cs = this; ps.setItem = function(key, value) {
if (useCookieStorage()) {
cs.get = function(state) { setCookie(key, value, cookieExpiration(5));
if (!state) { } else {
return; localStorage.setItem(key, value);
} }
}
var value = getCookie('kc-callback-' + state); ps.getItem = function(key) {
setCookie('kc-callback-' + state, '', cookieExpiration(-100)); if (useCookieStorage()) {
if (value) { return getCookie(key);
return JSON.parse(value);
} }
}; return localStorage.getItem(key);
}
cs.add = function(state) { ps.removeItem = function(key) {
setCookie('kc-callback-' + state.state, JSON.stringify(state), cookieExpiration(60)); if (typeof localStorage !== "undefined") {
}; try {
// Always try to delete from localStorage.
cs.removeItem = function(key) { localStorage.removeItem(key);
} catch (err) { }
}
// Always remove the cookie.
setCookie(key, '', cookieExpiration(-100)); setCookie(key, '', cookieExpiration(-100));
}; }
var cookieExpiration = function (minutes) { var cookieExpiration = function (minutes) {
var exp = new Date(); var exp = new Date();
exp.setTime(exp.getTime() + (minutes*60*1000)); exp.setTime(exp.getTime() + (minutes*60*1000));
return exp; return exp;
}; }
var getCookie = function (key) { var getCookie = function (key) {
var name = key + '='; var name = key + '=';
@@ -1142,22 +1050,13 @@
} }
} }
return ''; return '';
}; }
var setCookie = function (key, value, expirationDate) { var setCookie = function (key, value, expirationDate) {
var cookie = key + '=' + value + '; ' var cookie = key + '=' + value + '; '
+ 'expires=' + expirationDate.toUTCString() + '; '; + 'expires=' + expirationDate.toUTCString() + '; ';
document.cookie = cookie; document.cookie = cookie;
} }
};
function createCallbackStorage() {
try {
return new LocalStorage();
} catch (err) {
}
return new CookieStorage();
} }
var CallbackParser = function(uriToParse, responseMode) { var CallbackParser = function(uriToParse, responseMode) {
@@ -1204,7 +1103,7 @@
} }
var handleQueryParam = function(paramName, paramValue, oauth) { var handleQueryParam = function(paramName, paramValue, oauth) {
var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ]; var supportedOAuthParams = [ 'code', 'error', 'state' ];
for (var i = 0 ; i< supportedOAuthParams.length ; i++) { for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
if (paramName === supportedOAuthParams[i]) { if (paramName === supportedOAuthParams[i]) {
@@ -1230,9 +1129,12 @@
case 'redirect_fragment': case 'redirect_fragment':
oauth.fragment = queryParams[param]; oauth.fragment = queryParams[param];
break; break;
case 'prompt':
oauth.prompt = queryParams[param];
break;
default: default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) { if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + encodeURIComponent(queryParams[param]); oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
} }
break; break;
} }
Loaded 100 of 5830 files, more files were not shown because too many files have changed in this diff. Show more