Compare commits

..
Author SHA1 Message Date
keycloak-bot e677755bc5 Set version to 2.5.5.Final 2017-03-14 10:31:05 +00:00
Stian Thorgersen 399aa6aeb1 Merge pull request #3946 from pedroigor/authz-fixes-2.5.x
[2.5.x] Authz fixes
2017-03-14 11:28:23 +01:00
Pavel Drozd 0f4b1cd17f KEYCLOAK-4569 SSSDTest rewrited to be usable in different enviroments 2017-03-14 11:12:07 +01:00
Pavel Drozd 1f695f257f KEYCLOAK-4304 Updated Kerberos tests to be able to run them on different environment 2017-03-14 11:11:58 +01:00
mhajas 82d34fc53e KEYCLOAK-3961 2017-03-14 11:11:04 +01:00
mposolda b7fa16e64b KEYCLOAK-4285 Adapter tests for examples fail in Wildfly/EAP6 2017-03-14 11:01:31 +01:00
vramik 6b2ffb031c KEYCLOAK-4262 disable server-config-migration for distribution and add possibility to run it with profile 2017-03-14 10:59:19 +01:00
vramik 7c2e23b18b KEYCLOAK-4262 make AbstractSamlTest class abstract 2017-03-14 10:58:35 +01:00
vramik 0fb8217904 Enable server-config-migration for distribution 2017-03-14 10:57:56 +01:00
mhajas 3c692aa469 KEYCLOAK-4179 2017-03-14 10:57:38 +01:00
mhajas ebc8132dac KEYCLOAK-4181 2017-03-14 10:57:13 +01:00
mhajas e1031b96e5 KEYCLOAK-4261 2017-03-14 10:57:01 +01:00
mhajas 7400a1c321 KEYCLOAK-4160 2017-03-14 10:55:11 +01:00
mhajas f160a2013f KEYCLOAK-4262 Test for rejected consent 2017-03-14 10:54:34 +01:00
mhajas 4d52d085ad KEYCLOAK-4084 Fix ssl adapter tests 2017-03-14 10:53:52 +01:00
mhajas e4ae6f1418 KEYCLOAK-4329 Add test for empty KeyInfo 2017-03-14 10:53:18 +01:00
mhajas 21bbfbdc1d KEYCLOAK-3987 Add test for grant role from token 2017-03-14 10:52:43 +01:00
mhajas 8f600877f5 KEYCLOAK-4346 Validation tests are failing because of ban on server which provides some imports to SAML xsd 2017-03-14 10:52:14 +01:00
mhajas 24adeddf43 KEYCLOAK-4286 Use desired version of JS-adapter 2017-03-14 10:51:16 +01:00
mhajas 954c90370a KEYCLOAK-4020 add test for boolean attribute 2017-03-14 10:50:48 +01:00
vramik 811d1f2e99 resolve 'There is no context available for qualifier' message 2017-03-14 10:49:38 +01:00
vramik 25d2b27846 refactor asserts in ExportImportTest to be more verbose 2017-03-14 10:48:55 +01:00
vramik f0aa0298ec assumeCommunity for AccountPageTest.testLocalizedReferrerLinkContent 2017-03-14 10:48:45 +01:00
vramik 48f017e28c Removed unused import from LDAPBinaryAttributesTest 2017-03-14 10:48:34 +01:00
mhajas 0eeebef83c KEYCLOAK-3841 2017-03-14 10:47:32 +01:00
Pedro Igor c7db4ff650 [KEYCLOAK-4555] - Fixes and improvements to evaluation code 2017-03-13 15:22:02 -03:00
Pedro Igor 47bc81b015 [KEYCLOAK-4549] - Scope Permission Form is not showing saved resource 2017-03-13 15:21:54 -03:00
Stian Thorgersen 94aa04f7b0 Merge pull request #3933 from hmlnarik/KEYCLOAK-4552-2.5
KEYCLOAK-4552
2017-03-13 14:42:58 +01:00
Hynek Mlnarik d0d0ab0051 KEYCLOAK-4552 2017-03-10 13:32:19 +01:00
Stan Silvert 0df5338f2b KEYCLOAK-4514: Update migration scripts according to changes to the
subsystem
2017-03-10 13:18:35 +01:00
keycloak-bot 5cd2a564ae Set version to 2.5.5.Final-SNAPSHOT 2017-02-22 19:34:52 +00:00
keycloak-bot 81b9e99c5d Set version to 2.5.4.Final 2017-02-22 18:42:32 +00:00
Stian Thorgersen a4a6dd5049 KEYCLOAK-4483
Overlay standalone differs from dist standalone
2017-02-22 19:30:19 +01:00
Stian Thorgersen 401b2508a8 KEYCLOAK-4394 Use JBoss logging 2017-02-15 09:06:35 +01:00
Stian Thorgersen 709e7a6c9d Merge pull request #3861 from stianst/2.5.x
KEYCLOAK-4392 Copy component id from representation to model
2017-02-15 08:54:58 +01:00
Stian Thorgersen 1146d8dc5b Merge pull request #3865 from sebastienblanc/2.5.x-springboot-starter
KEYCLOAK-4423 : 2.5.x adding springboot starter
2017-02-15 08:54:15 +01:00
Stian Thorgersen 7e4eb7e87c Merge pull request #3863 from sebastienblanc/correct_version
Set version to 2.5.4.Final-SNAPSHOT
2017-02-14 14:51:16 +01:00
sebastienblanc c93eee1370 set version to 2.5.4.Final-SNAPSHOT 2017-02-14 14:42:20 +01:00
sebastienblanc 7ae154562e KEYCLOAK-4423 : Adding Spring Boot Adapter 2017-02-14 14:42:20 +01:00
sebastienblanc 14fdb38dbe Set version to 2.5.4.Final-SNAPSHOT 2017-02-14 14:14:21 +01:00
sebastienblanc a6c2ae2c52 added all the adapters in BOM 2017-02-14 13:10:56 +01:00
Stian Thorgersen 98c1020d4c KEYCLOAK-4195 Keycloak adapter and SPI bom 2017-02-14 13:10:44 +01:00
Anders Båtstrand 23791a8efd KEYCLOAK-4392 Copy component id from representation to model 2017-02-14 09:43:56 +01:00
Stian Thorgersen 52366aecc8 Merge pull request #3854 from pedroigor/RHSSO-767-2.5.x
[2.5.x][RHSSO-767] - Wrong implementation of Request.getRelativePath causing failures on Tomcat-like adapters
2017-02-13 10:26:37 +01:00
Stian Thorgersen 4c7f40c32c Merge pull request #3842 from mposolda/KEYCLOAK-4385-2.5.x
KEYCLOAK-4385 Added BundleBasedKeycloakConfigResolver
2017-02-13 10:25:42 +01:00
Pedro Igor 1f5a4a6758 [RHSSO-767] - Wrong implementation of Request.getRelativePath causing failures on Tomcat-like adapters 2017-02-09 21:29:01 -02:00
Stian Thorgersen 8cb17ed292 Merge pull request #3848 from stianst/KEYCLOAK-4378-2.5.x
KEYCLOAK-4378 New user attribute is not added after first login from …
2017-02-09 10:02:07 +01:00
Stian Thorgersen 68ca3d4959 Merge pull request #3836 from mposolda/KEYCLOAK-4371-2.5.x
KEYCLOAK-4371 Offline Tokens still useless When SSO Session Max is Re…
2017-02-08 15:42:28 +01:00
Stian Thorgersen 33742dbf62 Merge pull request #3830 from mposolda/2.5.x
KEYCLOAK-4364 Fix OpenLDAP issue with renaming DN
2017-02-08 15:40:39 +01:00
Stian Thorgersen 86fb74f00f Merge pull request #3815 from pedroigor/2.5.x
[2.5.x] Authz export fixes
2017-02-08 15:00:24 +01:00
Stian Thorgersen ac7b5a9708 Merge pull request #3844 from pedroigor/KEYCLOAK-4373-2.5.x
[2.5.x][KEYCLOAK-4373] - Adapter Feature Pack is missing keycloak-authz-client
2017-02-08 14:58:30 +01:00
Frederik Libert e45c7ce19f KEYCLOAK-4378 New user attribute is not added after first login from broker 2017-02-08 14:56:08 +01:00
Pedro Igor dde05cdb0e [KEYCLOAK-4373] - Adapter Feature Pack is missing keycloak-authz-client 2017-02-07 08:46:53 -02:00
mposolda 890f9aa393 KEYCLOAK-4385 Added BundleBasedKeycloakConfigResolver 2017-02-06 21:25:36 +01:00
Bill Burke 83804d5f2e Merge pull request #3840 from patriot1burke/2.5.x
KEYCLOAK-4381
2017-02-06 13:00:15 -05:00
Bill Burke 244f6ecbc7 KEYCLOAK-4381 2017-02-06 09:37:34 -05:00
Stian Thorgersen ac8ab80e01 Merge pull request #3833 from stianst/KEYCLOAK-4370-2.5.x
KEYCLOAK-4370 Prevent LDAP provider from being migrated twice
2017-02-06 08:21:27 +01:00
mposolda dcb56d6774 KEYCLOAK-4371 Offline Tokens still useless When SSO Session Max is Reached and normal userSession expired 2017-02-03 12:07:51 +01:00
Stian Thorgersen a57aa6257f KEYCLOAK-4370 Prevent LDAP provider from being migrated twice 2017-02-03 10:07:07 +01:00
Stian Thorgersen b913a18106 Merge pull request #3826 from stianst/KEYCLOAK-4338-2.5.x
Keycloak 4338 2.5.x
2017-02-03 08:58:47 +01:00
mposolda 27f9ec9381 KEYCLOAK-4364 Fix OpenLDAP issue with renaming DN 2017-02-02 22:23:10 +01:00
Stian Thorgersen 7a24fd87b0 Merge pull request #3822 from ssilvert/2.5.x
KEYCLOAK-4362 Split migration-domain script into two separate scripts
2017-02-02 21:08:29 +01:00
Stian Thorgersen 9c8443a5fc Merge pull request #3821 from patriot1burke/2.5.x
KEYCLOAK-4363
2017-02-02 11:32:33 +01:00
Stian Thorgersen 3a466df3ac KEYCLOAK-4366 Issues when keycloak.js is initialized with token 2017-02-02 10:58:38 +01:00
Stian Thorgersen e6f4e678a0 KEYCLOAK-4338 KEYCLOAK-4331 Fixes to session iframe 2017-02-02 10:58:38 +01:00
Stan Silvert 51868ba3bf KEYCLOAK-4262 Split migration-domain script into two separate scripts 2017-02-01 14:16:33 -05:00
Bill Burke 6fc1909cf0 KEYCLOAK-4363 2017-02-01 10:19:31 -05:00
Bill Burke 59ad412159 Merge pull request #3817 from patriot1burke/2.5.x
KEYCLOAK-4218
2017-02-01 08:55:20 -05:00
Bill Burke 42415db551 KEYCLOAK-4218 2017-01-31 15:14:52 -05:00
Pedro Igor 865af553cf [KEYCLOAK-4340] - Exporting authorization settings is updating policies with wrong data 2017-01-31 13:37:00 -02:00
Pedro Igor 18d45cc325 [KEYCLOAK-4341] - Resources are not properly exported when exporting authorization settings 2017-01-31 13:36:54 -02:00
Stian Thorgersen b697e3d4a9 Set version to 2.5.4.Final-SNAPSHOT 2017-01-30 15:21:06 +01:00
Stian Thorgersen 7d43e405d6 Set version to 2.5.3.Final 2017-01-30 15:19:35 +01:00
Stian Thorgersen cc195c469f Merge pull request #3808 from hmlnarik/KEYCLOAK-4329-NPE-when-not-providing-KeyInfo-element-in-IdP-initiated-SSO-SAML-2.5
KEYCLOAK-4329 Fix NPE when not providing KeyInfo element in IdP initiated SSO SAML
2017-01-30 15:15:35 +01:00
Hynek Mlnarik 8c5330f32e KEYCLOAK-4329 Fix NPE when not providing KeyInfo element in IdP initiated SSO SAML 2017-01-30 12:02:56 +01:00
Stian Thorgersen 8a5658a86c Set version to 2.5.3.Final-SNAPSHOT 2017-01-27 09:26:45 +01:00
Stian Thorgersen 7b0693671c Set version to 2.5.2.Final 2017-01-27 09:26:44 +01:00
Stian Thorgersen aea45e46da Merge pull request #3801 from mstruk/2.5.x
KEYCLOAK-4324 Upgrade and unify Aesh version to 0.66.12
2017-01-27 09:23:20 +01:00
Marko Strukelj 243ce2fc65 KEYCLOAK-4324 Upgrade and unify Aesh version to 0.66.12 2017-01-26 17:04:22 +01:00
keycloak-bot 1200955643 Set version to 2.5.2.Final-SNAPSHOT 2017-01-26 06:07:05 +00:00
keycloak-bot 1ad464c681 Set version to 2.5.1.Final 2017-01-26 05:17:37 +00:00
7693 changed files with 160102 additions and 1763532 deletions

No files matched your search

-6
View File
@@ -49,9 +49,3 @@ target
# Maven shade
#############
*dependency-reduced-pom.xml
# nodejs #
##########
# KEYCLOAK-5391: We will re-exclude node_modules when node_modules handling is worked out.
# For now, we keep our js libraries checked into GitHub, so we don't ignore.
#node_modules
+16 -20
View File
@@ -1,38 +1,34 @@
language: java
dist: precise
cache:
cache: false
directories:
- $HOME/.m2
before_cache:
- rm -rf $HOME/.m2/repository/org/keycloak
env:
global:
- MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx1536m"
- MAVEN_OPTS="-Xms512m -Xmx2048m"
matrix:
- TESTS=unit
- TESTS=server-group1
- TESTS=server-group2
- TESTS=server-group3
- TESTS=server-group4
- TESTS=group1
- TESTS=group2
- TESTS=group3
- TESTS=group4
- TESTS=adapter
- TESTS=old
- TESTS=crossdc
jdk:
- oraclejdk8
install: true
before_script:
- export MAVEN_SKIP_RC=true
before_install:
- "export PHANTOMJS_VERSION=2.1.1"
- "phantomjs --version"
- "export PATH=$PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin:$PATH"
- "phantomjs --version"
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then rm -rf $PWD/travis_phantomjs; mkdir -p $PWD/travis_phantomjs; fi"
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then wget https://github.com/Medium/phantomjs/releases/download/v$PHANTOMJS_VERSION/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -O $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2; fi"
- "if [ $(phantomjs --version) != '$PHANTOMJS_VERSION' ]; then tar -xvf $PWD/travis_phantomjs/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 -C $PWD/travis_phantomjs; fi"
- "phantomjs --version"
install:
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
script:
script:
- ./travis-run-tests.sh $TESTS
sudo: false
Regular → Executable
+4 -12
View File
@@ -32,31 +32,23 @@ To build the distribution run:
Once completed you will find distribution archives in `distribution`.
To build only the server run:
mvn -Pdistribution -pl distribution/server-dist -am -Dmaven.test.skip clean install
Starting Keycloak
-----------------
To start Keycloak during development first build as specified above, then run:
mvn -f testsuite/utils/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
cd keycloak-<VERSION>
tar xfz distribution/appliance-dist/target/keycloak-appliance-dist-all-<VERSION>.tar.gz
cd keycloak-appliance-dist-all-<VERSION>/keycloak
bin/standalone.sh
To stop the server press `Ctrl + C`.
Reporting security vulnerabilities
----------------------------------
If you've found a security vulnerability, please look at the [instructions on how to properly report it](http://www.keycloak.org/security.html)
Help and Documentation
----------------------
+2 -11
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -36,16 +36,7 @@
</keycloak.osgi.export>
<keycloak.osgi.import>
org.keycloak.*;version="${project.version}",
org.apache.http.auth.*;version=${apache.httpcomponents.version},
org.apache.http.client.*;version=${apache.httpcomponents.version},
org.apache.http.conn.*;version=${apache.httpcomponents.version},
org.apache.http.cookie.*;version=${apache.httpcomponents.version},
org.apache.http.impl.auth.*;version=${apache.httpcomponents.version},
org.apache.http.impl.client.*;version=${apache.httpcomponents.version},
org.apache.http.impl.conn.*;version=${apache.httpcomponents.version},
org.apache.http.impl.cookie.*;version=${apache.httpcomponents.version},
org.apache.http.impl.execchain.*;version=${apache.httpcomponents.version},
org.apache.http.*;version=${apache.httpcomponents.httpcore.version},
org.apache.http.*;version=${apache.httpcomponents.version},
org.apache.karaf.jaas.boot.principal;resolution:=optional,
org.apache.karaf.jaas.modules;resolution:=optional,
*;resolution:=optional
@@ -206,16 +206,6 @@ public class AdapterDeploymentContext {
public void setBearerOnly(boolean bearerOnly) {
delegate.setBearerOnly(bearerOnly);
}
@Override
public boolean isAutodetectBearerOnly() {
return delegate.isAutodetectBearerOnly();
}
@Override
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
delegate.setAutodetectBearerOnly(autodetectBearerOnly);
}
@Override
public boolean isEnableBasicAuth() {
@@ -287,16 +277,6 @@ public class AdapterDeploymentContext {
delegate.setSslRequired(sslRequired);
}
@Override
public int getConfidentialPort() {
return delegate.getConfidentialPort();
}
@Override
public void setConfidentialPort(int confidentialPort) {
delegate.setConfidentialPort(confidentialPort);
}
@Override
public TokenStore getTokenStore() {
return delegate.getTokenStore();
@@ -101,15 +101,6 @@ public class AuthenticatedActionsHandler {
if (!deployment.isCors()) return false;
KeycloakSecurityContext securityContext = facade.getSecurityContext();
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
String exposeHeaders = deployment.getCorsExposedHeaders();
if (deployment.getPolicyEnforcer() != null) {
if (exposeHeaders != null) {
exposeHeaders += ",";
}
exposeHeaders += "WWW-Authenticate";
}
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
@@ -133,9 +124,6 @@ public class AuthenticatedActionsHandler {
facade.getResponse().setStatus(200);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
if (exposeHeaders != null) {
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, exposeHeaders);
}
} else {
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
}
@@ -71,10 +71,9 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
AccessTokenResponse atr=null;
try {
String userpw=new String(Base64.decode(tokenString));
int seperatorIndex = userpw.indexOf(":");
String user = userpw.substring(0, seperatorIndex);
String pw = userpw.substring(seperatorIndex + 1);
atr = getToken(user, pw);
String[] parts=userpw.split(":");
atr = getToken(parts[0], parts[1]);
tokenString = atr.getToken();
} catch (Exception e) {
log.debug("Failed to obtain token", e);
@@ -83,8 +82,8 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat
}
return authenticateToken(exchange, atr.getToken());
}
}
private AccessTokenResponse getToken(String username, String password) throws Exception {
AccessTokenResponse tokenResponse=null;
HttpClient client = deployment.getClient();
@@ -23,8 +23,6 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import javax.security.cert.X509Certificate;
@@ -85,16 +83,6 @@ public class BearerTokenRequestAuthenticator {
}
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
log.debug("Verifying access_token");
if (log.isTraceEnabled()) {
try {
JWSInput jwsInput = new JWSInput(tokenString);
String wireString = jwsInput.getWireString();
log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
} catch (JWSInputException e) {
log.errorf(e, "Failed to parse access_token: %s", tokenString);
}
}
try {
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
} catch (VerificationException e) {
@@ -136,7 +124,6 @@ public class BearerTokenRequestAuthenticator {
}
surrogate = chain[0].getSubjectDN().getName();
}
log.debug("successful authorized");
return AuthOutcome.AUTHENTICATED;
}
@@ -174,19 +161,10 @@ public class BearerTokenRequestAuthenticator {
@Override
public boolean challenge(HttpFacade facade) {
if (deployment.getPolicyEnforcer() != null) {
deployment.getPolicyEnforcer().enforce(OIDCHttpFacade.class.cast(facade));
return true;
}
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
facade.getRequest().setError(error);
facade.getResponse().addHeader("WWW-Authenticate", challenge);
if(deployment.isDelegateBearerErrorResponseSending()){
facade.getResponse().setStatus(401);
}
else {
facade.getResponse().sendError(401);
}
facade.getResponse().sendError(401);
return true;
}
};
@@ -30,5 +30,4 @@ public interface CorsHeaders {
String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
}
@@ -57,8 +57,6 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
/**
* Abstraction for creating HttpClients. Allows SSL configuration.
@@ -172,8 +170,8 @@ public class HttpClientBuilder {
return this;
}
public HttpClientBuilder disableCookieCache(boolean disable) {
this.disableCookieCache = disable;
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
return this;
}
@@ -291,7 +289,6 @@ public class HttpClientBuilder {
cm = new SingleClientConnManager(registry);
}
BasicHttpParams params = new BasicHttpParams();
params.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
if (proxyHost != null) {
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
@@ -337,7 +334,7 @@ public class HttpClientBuilder {
}
public HttpClient build(AdapterHttpClientConfig 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();
if (truststorePath != null) {
@@ -35,8 +35,6 @@ import java.util.Map;
/**
* @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 $
*/
public class KeycloakDeployment {
@@ -68,7 +66,6 @@ public class KeycloakDeployment {
protected String scope;
protected SslRequired sslRequired = SslRequired.ALL;
protected int confidentialPort = -1;
protected TokenStore tokenStore = TokenStore.SESSION;
protected String stateCookieName = "OAuth_Token_Request_State";
protected boolean useResourceRoleMappings;
@@ -76,7 +73,6 @@ public class KeycloakDeployment {
protected int corsMaxAge = -1;
protected String corsAllowedHeaders;
protected String corsAllowedMethods;
protected String corsExposedHeaders;
protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup;
@@ -89,14 +85,6 @@ public class KeycloakDeployment {
protected int publicKeyCacheTtl;
private PolicyEnforcer policyEnforcer;
// https://tools.ietf.org/html/rfc7636
protected boolean pkce = false;
protected boolean ignoreOAuthQueryParameter;
protected Map<String, String> redirectRewriteRules;
protected boolean delegateBearerErrorResponseSending = false;
public KeycloakDeployment() {
}
@@ -154,8 +142,6 @@ public class KeycloakDeployment {
log.debug("resolveUrls");
}
authServerBaseUrl = authUrlBuilder.build().toString();
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
authUrl = KeycloakUriBuilder.fromUri(login);
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
@@ -280,14 +266,6 @@ public class KeycloakDeployment {
this.sslRequired = sslRequired;
}
public int getConfidentialPort() {
return confidentialPort;
}
public void setConfidentialPort(int confidentialPort) {
this.confidentialPort = confidentialPort;
}
public TokenStore getTokenStore() {
return tokenStore;
}
@@ -344,14 +322,6 @@ public class KeycloakDeployment {
this.corsAllowedMethods = corsAllowedMethods;
}
public String getCorsExposedHeaders() {
return corsExposedHeaders;
}
public void setCorsExposedHeaders(String corsExposedHeaders) {
this.corsExposedHeaders = corsExposedHeaders;
}
public boolean isExposeToken() {
return exposeToken;
}
@@ -444,37 +414,4 @@ public class KeycloakDeployment {
public PolicyEnforcer getPolicyEnforcer() {
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;
}
public boolean isDelegateBearerErrorResponseSending() {
return delegateBearerErrorResponseSending;
}
public void setDelegateBearerErrorResponseSending(boolean delegateBearerErrorResponseSending) {
this.delegateBearerErrorResponseSending = delegateBearerErrorResponseSending;
}
}
@@ -37,8 +37,6 @@ import java.security.PublicKey;
/**
* @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 $
*/
public class KeycloakDeploymentBuilder {
@@ -78,11 +76,6 @@ public class KeycloakDeploymentBuilder {
} else {
deployment.setSslRequired(SslRequired.EXTERNAL);
}
if (adapterConfig.getConfidentialPort() != -1) {
deployment.setConfidentialPort(adapterConfig.getConfidentialPort());
}
if (adapterConfig.getTokenStore() != null) {
deployment.setTokenStore(TokenStore.valueOf(adapterConfig.getTokenStore().toUpperCase()));
} else {
@@ -103,12 +96,6 @@ public class KeycloakDeploymentBuilder {
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
deployment.setCorsExposedHeaders(adapterConfig.getCorsExposedHeaders());
}
// https://tools.ietf.org/html/rfc7636
if (adapterConfig.isPkce()) {
deployment.setPkce(true);
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
@@ -120,8 +107,6 @@ public class KeycloakDeploymentBuilder {
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) {
throw new IllegalArgumentException("For bearer auth, you must set the realm-public-key or auth-server-url");
@@ -25,6 +25,7 @@ import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Encode;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import org.keycloak.constants.AdapterConstants;
@@ -37,10 +38,7 @@ import org.keycloak.representations.IDToken;
import org.keycloak.util.TokenUtil;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -66,7 +64,7 @@ public class OAuthRequestAuthenticator {
this.reqAuthenticator = requestAuthenticator;
this.facade = facade;
this.deployment = deployment;
this.sslRedirectPort = deployment.getConfidentialPort() != -1 ? deployment.getConfidentialPort() : sslRedirectPort;
this.sslRedirectPort = sslRedirectPort;
this.tokenStore = tokenStore;
}
@@ -143,7 +141,6 @@ public class OAuthRequestAuthenticator {
protected String getRedirectUri(String state) {
String url = getRequestUrl();
log.debugf("callback uri: %s", url);
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
int port = sslRedirectPort();
if (port < 0) {
@@ -170,13 +167,10 @@ public class OAuthRequestAuthenticator {
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
String uiLocales = getQueryParamValue(OAuth2Constants.UI_LOCALES_PARAM);
url = UriUtils.stripQueryParam(url, OAuth2Constants.UI_LOCALES_PARAM);
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, rewrittenRedirectUri(url))
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
.queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){
@@ -191,9 +185,6 @@ public class OAuthRequestAuthenticator {
if (maxAge != null && maxAge.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
}
if (uiLocales != null && uiLocales.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
}
scope = TokenUtil.attachOIDCScope(scope);
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
@@ -329,15 +320,14 @@ public class OAuthRequestAuthenticator {
AccessTokenResponse tokenResponse = null;
strippedOauthParametersRequestUri = stripOauthParametersFromRedirect();
try {
// 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;
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, rewrittenRedirectUri(strippedOauthParametersRequestUri), httpSessionId);
tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus());
if (failure.getError() != null) {
if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError());
}
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
@@ -350,14 +340,6 @@ public class OAuthRequestAuthenticator {
tokenString = tokenResponse.getToken();
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
log.debug("Verifying tokens");
if (log.isTraceEnabled()) {
logToken("\taccess_token", tokenString);
logToken("\tid_token", idTokenString);
logToken("\trefresh_token", refreshToken);
}
try {
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
if (idTokenString != null) {
@@ -390,36 +372,9 @@ public class OAuthRequestAuthenticator {
protected String stripOauthParametersFromRedirect() {
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(facade.getRequest().getURI())
.replaceQueryParam(OAuth2Constants.CODE, null)
.replaceQueryParam(OAuth2Constants.STATE, null)
.replaceQueryParam(OAuth2Constants.SESSION_STATE, null);
.replaceQueryParam(OAuth2Constants.STATE, null);
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;
}
private void logToken(String name, String token) {
try {
JWSInput jwsInput = new JWSInput(token);
String wireString = jwsInput.getWireString();
log.tracef("\t%s: %s", name, wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
} catch (JWSInputException e) {
log.errorf(e, "Failed to parse %s: %s", name, token);
}
}
}
@@ -22,8 +22,6 @@ 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 {
@@ -35,9 +33,6 @@ public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAu
}
public AuthOutcome authenticate(HttpFacade exchange) {
if(!deployment.isOAuthQueryParameterEnabled()) {
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
tokenString = getAccessTokenFromQueryParamter(exchange);
if (tokenString == null || tokenString.trim().isEmpty()) {
@@ -155,9 +155,7 @@ public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext
this.refreshToken = response.getRefreshToken();
}
this.tokenString = tokenString;
if (tokenStore != null) {
tokenStore.refreshCallback(this);
}
tokenStore.refreshCallback(this);
return true;
}
@@ -24,7 +24,6 @@ import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.enums.SslRequired;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -161,8 +160,7 @@ public abstract class RequestAuthenticator {
protected boolean verifySSL() {
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 .",
facade.getRequest().getRemoteAddr(), facade.getRequest().isSecure(), deployment.getSslRequired().name());
log.warn("SSL is required to authenticate");
return true;
}
return false;
@@ -33,8 +33,6 @@ import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.jboss.logging.Logger;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -48,8 +46,6 @@ import java.util.List;
*/
public class ServerRequest {
private static Logger logger = Logger.getLogger(ServerRequest.class);
public static class HttpFailure extends Exception {
private int status;
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 {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
@@ -32,7 +32,7 @@ import java.util.Map;
*/
public class ClientIdAndSecretCredentialsProvider implements ClientCredentialsProvider {
private static Logger logger = Logger.getLogger(ClientIdAndSecretCredentialsProvider.class);
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
public static final String PROVIDER_ID = CredentialRepresentation.SECRET;
@@ -1,97 +0,0 @@
package org.keycloak.adapters.authentication;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.representations.JsonWebToken;
/**
* Client authentication based on JWT signed by client secret instead of private key .
* See <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">specs</a> for more details.
*
* @author <a href="mailto:takashi.norimatsu.ws@hitachi.com">Takashi Norimatsu</a>
*/
public class JWTClientSecretCredentialsProvider implements ClientCredentialsProvider {
private static final Logger logger = Logger.getLogger(JWTClientSecretCredentialsProvider.class);
public static final String PROVIDER_ID = "secret-jwt";
private SecretKey clientSecret;
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(KeycloakDeployment deployment, Object config) {
if (config == null || !(config instanceof Map)) {
throw new RuntimeException("Configuration of jwt credentials by client secret is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
}
Map<String, Object> cfg = (Map<String, Object>) config;
String clientSecretString = (String) cfg.get("secret");
if (clientSecretString == null) {
throw new RuntimeException("Missing parameter secret-jwt in configuration of jwt for client " + deployment.getResourceName());
}
setClientSecret(clientSecretString);
}
@Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
}
public void setClientSecret(String clientSecretString) {
// Get client secret and validate signature
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
// The HMAC (Hash-based Message Authentication Code) is calculated using the octets of the UTF-8 representation of the client_secret as the shared key.
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>
// because it must be implemented in every java platform.
try {
clientSecret = new SecretKeySpec(clientSecretString.getBytes("UTF-8"), "HmacSHA256");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Failed to create secret key spec due to unsupported encoding.");
}
}
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
// JOSE header {"alg":"HS256","typ" : "JWT"} no need "kid" due to using only one registered client secret.
// Use "HmacSHA256" consulting <a href="https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Mac.html">java8 api</a>.
// because it must be implemented in every java platform.
return new JWSBuilder().jsonContent(jwt).hmac256(clientSecret);
}
private JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
// According to <a href="http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication">OIDC's client authentication spec</a>,
// JWT claims is the same as one by private_key_jwt
JsonWebToken reqToken = new JsonWebToken();
reqToken.id(AdapterUtils.generateId());
reqToken.issuer(clientId);
reqToken.subject(clientId);
reqToken.audience(realmInfoUrl);
int now = Time.currentTime();
reqToken.issuedAt(now);
// the same as in KEYCLOAK-2986, JWTClientCredentialsProvider's timeout field
reqToken.expiration(now + 10);
reqToken.notBefore(now);
return reqToken;
}
}
@@ -17,28 +17,26 @@
*/
package org.keycloak.adapters.authorization;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
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.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.Authorization;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.MethodConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.ScopeEnforcementMode;
import org.keycloak.representations.idm.authorization.Permission;
/**
@@ -47,114 +45,98 @@ import org.keycloak.representations.idm.authorization.Permission;
public abstract class AbstractPolicyEnforcer {
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
private static final String HTTP_METHOD_DELETE = "DELETE";
private final PolicyEnforcerConfig enforcerConfig;
private final PolicyEnforcer policyEnforcer;
protected AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
private Map<String, PathConfig> paths;
private AuthzClient authzClient;
private PathMatcher pathMatcher;
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
this.authzClient = policyEnforcer.getClient();
this.pathMatcher = new PathMatcher();
this.paths = policyEnforcer.getPaths();
}
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
EnforcementMode enforcementMode = getEnforcerConfig().getEnforcementMode();
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
return createEmptyAuthorizationContext(true);
}
Request request = httpFacade.getRequest();
PathConfig pathConfig = getPathConfig(request);
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
if (securityContext == null) {
if (!isDefaultAccessDeniedUri(request)) {
if (pathConfig != null) {
challenge(pathConfig, getRequiredScopes(pathConfig, request), httpFacade);
} else {
handleAccessDenied(httpFacade);
}
}
return createEmptyAuthorizationContext(false);
}
if (securityContext != null) {
AccessToken accessToken = securityContext.getToken();
AccessToken accessToken = securityContext.getToken();
if (accessToken != null) {
Request request = httpFacade.getRequest();
Response response = httpFacade.getResponse();
String path = getPath(request);
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
if (accessToken != null) {
if (LOGGER.isDebugEnabled()) {
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);
if (pathConfig == null) {
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
return createAuthorizationContext(accessToken);
}
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);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Could not find a configuration for path [%s]", getPath(request));
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createEmptyAuthorizationContext(true);
}
if (isDefaultAccessDeniedUri(request)) {
return createAuthorizationContext(accessToken, null);
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);
}
}
handleAccessDenied(httpFacade);
return createEmptyAuthorizationContext(false);
}
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
return createAuthorizationContext(accessToken, pathConfig);
}
MethodConfig methodConfig = getRequiredScopes(pathConfig, request);
if (isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
try {
return createAuthorizationContext(accessToken, pathConfig);
} catch (Exception e) {
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
response.sendError(403, "Authorization failed.");
}
}
if (methodConfig != null && ScopeEnforcementMode.DISABLED.equals(methodConfig.getScopesEnforcementMode())) {
return createEmptyAuthorizationContext(true);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
}
if (!challenge(pathConfig, methodConfig, httpFacade)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Challenge not sent, sending default forbidden response. Path [%s]", pathConfig);
}
handleAccessDenied(httpFacade);
}
}
return createEmptyAuthorizationContext(false);
}
protected abstract boolean challenge(PathConfig pathConfig, MethodConfig methodConfig, OIDCHttpFacade facade);
protected abstract boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade);
protected boolean isAuthorized(PathConfig actualPathConfig, MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
Request request = httpFacade.getRequest();
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
if (isDefaultAccessDeniedUri(request)) {
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
return true;
}
Authorization authorization = accessToken.getAuthorization();
AccessToken.Authorization authorization = accessToken.getAuthorization();
if (authorization == null) {
return false;
}
List<Permission> permissions = authorization.getPermissions();
boolean hasPermission = false;
List<Permission> grantedPermissions = authorization.getPermissions();
for (Permission permission : grantedPermissions) {
if (permission.getResourceId() != null) {
for (Permission permission : permissions) {
if (permission.getResourceSetId() != null) {
if (isResourcePermission(actualPathConfig, permission)) {
hasPermission = true;
@@ -162,19 +144,16 @@ public abstract class AbstractPolicyEnforcer {
continue;
}
if (hasResourceScopePermission(methodConfig, permission)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, grantedPermissions);
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
this.paths.remove(actualPathConfig);
}
if (HTTP_METHOD_DELETE.equalsIgnoreCase(request.getMethod()) && actualPathConfig.isInstance()) {
policyEnforcer.getPathMatcher().removeFromCache(getPath(request));
}
return hasValidClaims(actualPathConfig, permission, httpFacade, authorization);
return true;
}
}
} else {
if (hasResourceScopePermission(methodConfig, permission)) {
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
hasPermission = true;
return true;
}
@@ -185,83 +164,41 @@ public abstract class AbstractPolicyEnforcer {
return true;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugf("Authorization FAILED for path [%s]. Not enough permissions [%s].", actualPathConfig, grantedPermissions);
}
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
return false;
}
private boolean hasValidClaims(PathConfig actualPathConfig, Permission permission, OIDCHttpFacade httpFacade, Authorization authorization) {
Map<String, Set<String>> grantedClaims = permission.getClaims();
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
if (grantedClaims != null) {
Map<String, List<String>> claims = resolveClaims(actualPathConfig, httpFacade);
if (claims.isEmpty()) {
return false;
}
for (Entry<String, Set<String>> entry : grantedClaims.entrySet()) {
List<String> requestClaims = claims.get(entry.getKey());
if (requestClaims == null || requestClaims.isEmpty() || !entry.getValue().containsAll(requestClaims)) {
return false;
}
if (accessDeniedPath != null) {
if (request.getURI().contains(accessDeniedPath)) {
return true;
}
}
return true;
return false;
}
protected void handleAccessDenied(OIDCHttpFacade httpFacade) {
httpFacade.getResponse().sendError(403);
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
Set<String> allowedScopes = permission.getScopes();
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
}
protected AuthzClient getAuthzClient() {
return policyEnforcer.getClient();
return this.authzClient;
}
protected PolicyEnforcerConfig getEnforcerConfig() {
return policyEnforcer.getEnforcerConfig();
return enforcerConfig;
}
protected PolicyEnforcer getPolicyEnforcer() {
return policyEnforcer;
}
private boolean isDefaultAccessDeniedUri(Request request) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
return accessDeniedPath != null && request.getURI().contains(accessDeniedPath);
}
private boolean hasResourceScopePermission(MethodConfig methodConfig, Permission permission) {
List<String> requiredScopes = methodConfig.getScopes();
Set<String> allowedScopes = permission.getScopes();
if (allowedScopes.isEmpty()) {
return true;
}
PolicyEnforcerConfig.ScopeEnforcementMode enforcementMode = methodConfig.getScopesEnforcementMode();
if (PolicyEnforcerConfig.ScopeEnforcementMode.ALL.equals(enforcementMode)) {
return allowedScopes.containsAll(requiredScopes);
}
if (PolicyEnforcerConfig.ScopeEnforcementMode.ANY.equals(enforcementMode)) {
for (String requiredScope : requiredScopes) {
if (allowedScopes.contains(requiredScope)) {
return true;
}
}
}
return requiredScopes.isEmpty();
}
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
return new ClientAuthorizationContext(getAuthzClient()) {
return new AuthorizationContext() {
@Override
public boolean hasPermission(String resourceName, String scopeName) {
return granted;
@@ -289,30 +226,54 @@ public abstract class AbstractPolicyEnforcer {
};
}
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
String path = getPath(request);
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());
this.policyEnforcer.addPath(config);
return config;
}
}
return originalConfig;
}
private String getPath(Request request) {
return request.getRelativePath();
}
private MethodConfig getRequiredScopes(PathConfig pathConfig, Request request) {
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
Set<String> requiredScopes = new HashSet<>();
requiredScopes.addAll(pathConfig.getScopes());
String method = request.getMethod();
for (MethodConfig methodConfig : pathConfig.getMethods()) {
for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
if (methodConfig.getMethod().equals(method)) {
return methodConfig;
requiredScopes.addAll(methodConfig.getScopes());
}
}
MethodConfig methodConfig = new MethodConfig();
methodConfig.setMethod(request.getMethod());
methodConfig.setScopes(pathConfig.getScopes());
methodConfig.setScopesEnforcementMode(PolicyEnforcerConfig.ScopeEnforcementMode.ANY);
return methodConfig;
return requiredScopes;
}
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
return new ClientAuthorizationContext(accessToken, pathConfig, getAuthzClient());
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
return new AuthorizationContext(accessToken, this.paths);
}
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
@@ -328,34 +289,6 @@ public abstract class AbstractPolicyEnforcer {
}
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
return permission.getResourceId().equals(actualPathConfig.getId());
}
private PathConfig getPathConfig(Request request) {
return isDefaultAccessDeniedUri(request) ? null : policyEnforcer.getPathMatcher().matches(getPath(request));
}
protected Map<String, List<String>> resolveClaims(PathConfig pathConfig, OIDCHttpFacade httpFacade) {
Map<String, List<String>> claims = getClaims(getEnforcerConfig().getClaimInformationPointConfig(), httpFacade);
claims.putAll(getClaims(pathConfig.getClaimInformationPointConfig(), httpFacade));
return claims;
}
private Map<String, List<String>> getClaims(Map<String, Map<String, Object>>claimInformationPointConfig, HttpFacade httpFacade) {
Map<String, List<String>> claims = new HashMap<>();
if (claimInformationPointConfig != null) {
for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());
if (factory != null) {
claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
}
}
}
return claims;
return permission.getResourceSetId().equals(actualPathConfig.getId());
}
}
@@ -0,0 +1,79 @@
/*
* 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 org.jboss.logging.Logger;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.representation.PermissionRequest;
import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
private static Logger LOGGER = Logger.getLogger(BearerTokenPolicyEnforcer.class);
public BearerTokenPolicyEnforcer(PolicyEnforcer enforcer) {
super(enforcer);
}
@Override
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
} else {
challengeEntitlementAuthentication(facade);
}
return true;
}
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
}
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
HttpFacade.Response response = facade.getResponse();
AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
String clientId = authzClient.getConfiguration().getClientId();
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
response.setStatus(401);
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
}
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setScopes(requiredScopes);
return permission.forResource(permissionRequest).getTicket();
}
}
@@ -1,30 +0,0 @@
/*
* Copyright 2018 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.List;
import java.util.Map;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ClaimInformationPointProvider {
Map<String, List<String>> resolve(HttpFacade httpFacade);
}
@@ -1,33 +0,0 @@
/*
* Copyright 2018 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.Map;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface ClaimInformationPointProviderFactory<C extends ClaimInformationPointProvider> {
String getName();
void init(PolicyEnforcer policyEnforcer);
C create(Map<String, Object> config);
}
@@ -18,30 +18,25 @@
package org.keycloak.adapters.authorization;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.AuthorizationDeniedException;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.resource.PermissionResource;
import org.keycloak.authorization.client.resource.ProtectionResource;
import org.keycloak.common.util.Base64;
import org.keycloak.authorization.client.representation.AuthorizationRequest;
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.PermissionRequest;
import org.keycloak.authorization.client.representation.PermissionResponse;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.AuthorizationRequest;
import org.keycloak.representations.idm.authorization.AuthorizationResponse;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.PermissionRequest;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -55,14 +50,14 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
}
@Override
protected boolean isAuthorized(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AccessToken accessToken, OIDCHttpFacade httpFacade) {
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
AccessToken original = accessToken;
if (super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade)) {
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
return true;
}
accessToken = requestAuthorizationToken(pathConfig, methodConfig, httpFacade);
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
if (accessToken == null) {
return false;
@@ -78,50 +73,16 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
if (newAuthorization != null) {
List<Permission> grantedPermissions = authorization.getPermissions();
List<Permission> newPermissions = newAuthorization.getPermissions();
for (Permission newPermission : newPermissions) {
if (!grantedPermissions.contains(newPermission)) {
grantedPermissions.add(newPermission);
}
}
authorization.getPermissions().addAll(newAuthorization.getPermissions());
}
original.setAuthorization(authorization);
return super.isAuthorized(pathConfig, methodConfig, accessToken, httpFacade);
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
}
@Override
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
if (isBearerAuthorization(httpFacade)) {
HttpFacade.Response response = httpFacade.getResponse();
AuthzClient authzClient = getAuthzClient();
String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);
if (ticket != null) {
response.setStatus(401);
response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
.append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
} else {
response.setStatus(403);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Sending challenge");
}
return true;
}
handleAccessDenied(httpFacade);
return true;
}
@Override
protected void handleAccessDenied(OIDCHttpFacade facade) {
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
HttpFacade.Response response = facade.getResponse();
@@ -131,90 +92,56 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
} else {
response.sendError(403);
}
return true;
}
private AccessToken requestAuthorizationToken(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
return null;
}
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
try {
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
String accessTokenString = securityContext.getTokenString();
String accessToken = httpFacade.getSecurityContext().getTokenString();
AuthzClient authzClient = getAuthzClient();
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
AccessToken accessToken = securityContext.getToken();
AuthorizationRequest authzRequest = new AuthorizationRequest();
if (isBearerAuthorization(httpFacade) || accessToken.getAuthorization() != null) {
authzRequest.addPermission(pathConfig.getId(), methodConfig.getScopes());
}
if (getEnforcerConfig().getUserManagedAccess() != null) {
LOGGER.debug("Obtaining authorization for authenticated user.");
PermissionRequest permissionRequest = new PermissionRequest();
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
permissionRequest.setResourceSetId(pathConfig.getId());
permissionRequest.setScopes(requiredScopes);
if (!claims.isEmpty()) {
authzRequest.setClaimTokenFormat("urn:ietf:params:oauth:token-type:jwt");
authzRequest.setClaimToken(Base64.encodeBytes(JsonSerialization.writeValueAsBytes(claims)));
}
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
if (accessToken.getAuthorization() != null) {
authzRequest.setRpt(accessTokenString);
}
if (authzResponse != null) {
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
LOGGER.debug("Obtaining authorization for authenticated user.");
AuthorizationResponse authzResponse;
if (isBearerAuthorization(httpFacade)) {
authzRequest.setSubjectToken(accessTokenString);
authzResponse = getAuthzClient().authorization().authorize(authzRequest);
return null;
} else {
authzResponse = getAuthzClient().authorization(accessTokenString).authorize(authzRequest);
}
LOGGER.debug("Obtaining entitlements for authenticated user.");
AccessToken token = httpFacade.getSecurityContext().getToken();
if (authzResponse != null) {
return AdapterRSATokenVerifier.verifyToken(authzResponse.getToken(), deployment);
if (token.getAuthorization() == null) {
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
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().getClientId(), request);
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
}
}
} catch (AuthorizationDeniedException ignore) {
LOGGER.debug("Authorization denied", ignore);
} catch (AuthorizationDeniedException e) {
LOGGER.debug("Authorization denied", e);
return null;
} catch (Exception e) {
throw new RuntimeException("Unexpected error during authorization request.", e);
}
return null;
}
private String getPermissionTicket(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, AuthzClient authzClient, OIDCHttpFacade httpFacade) {
if (getEnforcerConfig().getUserManagedAccess() != null) {
ProtectionResource protection = authzClient.protection();
PermissionResource permission = protection.permission();
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setResourceId(pathConfig.getId());
permissionRequest.setScopes(new HashSet<>(methodConfig.getScopes()));
Map<String, List<String>> claims = resolveClaims(pathConfig, httpFacade);
if (!claims.isEmpty()) {
permissionRequest.setClaims(claims);
}
return permission.create(permissionRequest).getTicket();
}
return null;
}
private boolean isBearerAuthorization(OIDCHttpFacade httpFacade) {
List<String> authHeaders = httpFacade.getRequest().getHeaders("Authorization");
if (authHeaders != null) {
for (String authHeader : authHeaders) {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) continue;
if (!split[0].equalsIgnoreCase("Bearer")) continue;
return true;
}
}
return getPolicyEnforcer().getDeployment().isBearerOnly();
}
}
@@ -1,173 +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;
/**
* A simple LRU cache implementation supporting expiration and maximum number of entries.
*
* @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;
private final boolean enabled;
/**
* 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;
this.enabled = maxAge > 0;
}
public void put(String uri, PathConfig newValue) {
if (!enabled) {
return;
}
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 boolean containsKey(String uri) {
return cache.containsKey(uri);
}
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;
}
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.adapters.authorization;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import java.util.Map;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
class PathMatcher {
private static final String ANY_RESOURCE_PATTERN = "/*";
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
PathConfig pathConfig = paths.get(requestedUri);
if (pathConfig != null) {
return pathConfig;
}
PathConfig actualConfig = null;
for (PathConfig entry : paths.values()) {
String protectedUri = entry.getPath();
String selectedUri = null;
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
selectedUri = protectedUri;
}
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
if (suffixIndex != -1) {
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
if (requestedUri.endsWith(protectedSuffix)) {
selectedUri = protectedUri;
}
}
if (protectedUri.equals(requestedUri)) {
selectedUri = protectedUri;
}
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
String formattedPattern = removeWildCardsFromUri(protectedUri);
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
selectedUri = protectedUri;
}
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
selectedUri = protectedUri;
}
}
int startRegex = protectedUri.indexOf('{');
if (startRegex != -1) {
String prefix = protectedUri.substring(0, startRegex);
if (requestedUri.startsWith(prefix)) {
selectedUri = protectedUri;
}
}
if (selectedUri != null) {
selectedUri = protectedUri;
}
if (selectedUri != null) {
if (actualConfig == null) {
actualConfig = entry;
} else {
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
actualConfig = entry;
}
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
actualConfig = entry;
}
}
}
}
return actualConfig;
}
private String removeWildCardsFromUri(String protectedUri) {
return protectedUri.replaceAll("/[*]", "/");
}
}
@@ -17,33 +17,28 @@
*/
package org.keycloak.adapters.authorization;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import org.jboss.logging.Logger;
import org.keycloak.AuthorizationContext;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.ClientAuthenticator;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.authorization.client.representation.RegistrationResponse;
import org.keycloak.authorization.client.representation.ResourceRepresentation;
import org.keycloak.authorization.client.representation.ScopeRepresentation;
import org.keycloak.authorization.client.resource.ProtectedResource;
import org.keycloak.common.util.PathMatcher;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCacheConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
import org.keycloak.representations.idm.authorization.Permission;
import org.keycloak.representations.idm.authorization.ResourceRepresentation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
@@ -55,27 +50,13 @@ public class PolicyEnforcer {
private final KeycloakDeployment deployment;
private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig;
private final PathConfigMatcher pathMatcher;
private final Map<String, PathConfig> paths;
private final Map<String, ClaimInformationPointProviderFactory> claimInformationPointProviderFactories = new HashMap<>();
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
this.deployment = deployment;
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
Configuration configuration = new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient());
this.authzClient = AuthzClient.create(configuration, new ClientAuthenticator() {
@Override
public void configureClientCredentials(Map<String, List<String>> requestParams, Map<String, String> requestHeaders) {
Map<String, String> formparams = new HashMap<>();
ClientCredentialsProviderUtils.setClientCredentials(PolicyEnforcer.this.deployment, requestHeaders, formparams);
for (Entry<String, String> param : formparams.entrySet()) {
requestParams.put(param.getKey(), Arrays.asList(param.getValue()));
}
}
});
paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
pathMatcher = new PathConfigMatcher(paths, enforcerConfig, authzClient);
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Initialization complete. Path configurations:");
@@ -83,17 +64,20 @@ public class PolicyEnforcer {
LOGGER.debug(pathConfig);
}
}
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, ClaimInformationPointProviderFactory.class.getClassLoader()));
loadClaimInformationPointProviders(ServiceLoader.load(ClaimInformationPointProviderFactory.class, Thread.currentThread().getContextClassLoader()));
}
public AuthorizationContext enforce(OIDCHttpFacade facade) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debugv("Policy enforcement is enabled. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
LOGGER.debugv("Policy enforcement is enable. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
}
AuthorizationContext context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
AuthorizationContext context;
if (deployment.isBearerOnly()) {
context = new BearerTokenPolicyEnforcer(this).authorize(facade);
} else {
context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
@@ -106,11 +90,11 @@ public class PolicyEnforcer {
return context;
}
public PolicyEnforcerConfig getEnforcerConfig() {
PolicyEnforcerConfig getEnforcerConfig() {
return enforcerConfig;
}
public AuthzClient getClient() {
AuthzClient getClient() {
return authzClient;
}
@@ -118,30 +102,14 @@ public class PolicyEnforcer {
return paths;
}
public PathConfigMatcher getPathMatcher() {
return pathMatcher;
void addPath(PathConfig pathConfig) {
paths.put(pathConfig.getPath(), pathConfig);
}
public KeycloakDeployment getDeployment() {
KeycloakDeployment getDeployment() {
return deployment;
}
public Map<String, ClaimInformationPointProviderFactory> getClaimInformationPointProviderFactories() {
return claimInformationPointProviderFactories;
}
private void loadClaimInformationPointProviders(ServiceLoader<ClaimInformationPointProviderFactory> loader) {
Iterator<ClaimInformationPointProviderFactory> iterator = loader.iterator();
while (iterator.hasNext()) {
ClaimInformationPointProviderFactory factory = iterator.next();
factory.init(this);
claimInformationPointProviderFactories.put(factory.getName(), factory);
}
}
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
boolean loadPathsFromServer = true;
@@ -154,11 +122,7 @@ public class PolicyEnforcer {
if (loadPathsFromServer) {
LOGGER.info("No path provided in configuration.");
Map<String, PathConfig> paths = configureAllPathsForResourceServer(protectedResource);
paths.putAll(configureDefinedPaths(protectedResource, enforcerConfig));
return paths;
return configureAllPathsForResourceServer(protectedResource);
} else {
LOGGER.info("Paths provided in configuration.");
return configureDefinedPaths(protectedResource, enforcerConfig);
@@ -166,41 +130,56 @@ public class PolicyEnforcer {
}
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
Map<String, PathConfig> paths = Collections.synchronizedMap(new LinkedHashMap<String, PathConfig>());
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
ResourceRepresentation resource;
Set<String> search;
String resourceName = pathConfig.getName();
String path = pathConfig.getPath();
if (resourceName != null) {
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
resource = protectedResource.findByName(resourceName);
search = protectedResource.findByFilter("name=" + resourceName);
} else {
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
List<ResourceRepresentation> resources = protectedResource.findByUri(path);
if (resources.isEmpty()) {
resources = protectedResource.findByMatchingUri(path);
}
if (resources.size() == 1) {
resource = resources.get(0);
} else if (resources.size() > 1) {
throw new RuntimeException("Multiple resources found with the same uri");
} else {
resource = null;
}
search = protectedResource.findByFilter("uri=" + path);
}
if (resource != null) {
pathConfig.setId(resource.getId());
if (search.isEmpty()) {
if (enforcerConfig.isCreateResources()) {
LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
ResourceRepresentation resource = new ResourceRepresentation();
resource.setName(resourceName);
resource.setType(pathConfig.getType());
resource.setUri(path);
HashSet<ScopeRepresentation> scopes = new HashSet<>();
for (String scopeName : pathConfig.getScopes()) {
ScopeRepresentation scope = new ScopeRepresentation();
scope.setName(scopeName);
scopes.add(scope);
}
resource.setScopes(scopes);
RegistrationResponse registrationResponse = protectedResource.create(resource);
pathConfig.setId(registrationResponse.getId());
} 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.");
}
} else {
pathConfig.setId(search.iterator().next());
}
PathConfig existingPath = null;
for (PathConfig current : paths.values()) {
if (current.getPath().equals(pathConfig.getPath())) {
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
existingPath = current;
break;
}
@@ -221,125 +200,35 @@ public class PolicyEnforcer {
LOGGER.info("Querying the server for all resources associated with this application.");
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
if (!enforcerConfig.getLazyLoadPaths()) {
for (String id : protectedResource.findAll()) {
ResourceRepresentation resourceDescription = protectedResource.findById(id);
for (String id : protectedResource.findAll()) {
RegistrationResponse response = protectedResource.findById(id);
ResourceRepresentation resourceDescription = response.getResourceDescription();
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = PathConfig.createPathConfig(resourceDescription);
paths.put(pathConfig.getPath(), pathConfig);
}
if (resourceDescription.getUri() != null) {
PathConfig pathConfig = createPathConfig(resourceDescription);
paths.put(pathConfig.getPath(), pathConfig);
}
}
return paths;
}
public class PathConfigMatcher extends PathMatcher<PathConfig> {
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
PathConfig pathConfig = new PathConfig();
private final Map<String, PathConfig> paths;
private final PathCache pathCache;
private final AuthzClient authzClient;
private final PolicyEnforcerConfig enforcerConfig;
pathConfig.setId(resourceDescription.getId());
pathConfig.setName(resourceDescription.getName());
pathConfig.setPath(resourceDescription.getUri());
public PathConfigMatcher(Map<String, PathConfig> paths, PolicyEnforcerConfig enforcerConfig, AuthzClient authzClient) {
this.paths = paths;
this.enforcerConfig = enforcerConfig;
PathCacheConfig cacheConfig = enforcerConfig.getPathCacheConfig();
List<String> scopeNames = new ArrayList<>();
if (cacheConfig == null) {
cacheConfig = new PathCacheConfig();
}
pathCache = new PathCache(cacheConfig.getMaxEntries(), cacheConfig.getLifespan());
this.authzClient = authzClient;
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
scopeNames.add(scope.getName());
}
@Override
public PathConfig matches(String targetUri) {
PathConfig pathConfig = pathCache.get(targetUri);
pathConfig.setScopes(scopeNames);
pathConfig.setType(resourceDescription.getType());
if (pathCache.containsKey(targetUri) || pathConfig != null) {
return pathConfig;
}
pathConfig = super.matches(targetUri);
if (enforcerConfig.getLazyLoadPaths() || enforcerConfig.getPathCacheConfig() != null) {
if ((pathConfig == null || (pathConfig.getPath().contains("*")))) {
try {
List<ResourceRepresentation> matchingResources = authzClient.protection().resource().findByMatchingUri(targetUri);
if (!matchingResources.isEmpty()) {
Map<String, Map<String, Object>> cipConfig = null;
if (pathConfig != null) {
cipConfig = pathConfig.getClaimInformationPointConfig();
}
pathConfig = PathConfig.createPathConfig(matchingResources.get(0));
if (cipConfig != null) {
pathConfig.setClaimInformationPointConfig(cipConfig);
}
}
} catch (Exception cause) {
LOGGER.errorf(cause, "Could not lazy load resource with path [" + targetUri + "] from server");
return null;
}
}
}
pathCache.put(targetUri, pathConfig);
return pathConfig;
}
@Override
protected String getPath(PathConfig entry) {
return entry.getPath();
}
@Override
protected Collection<PathConfig> getPaths() {
return paths.values();
}
@Override
protected PathConfig resolvePathConfig(PathConfig originalConfig, String path) {
if (originalConfig.hasPattern()) {
ProtectedResource resource = authzClient.protection().resource();
// search by an exact match
List<ResourceRepresentation> search = resource.findByUri(path);
// if exact match not found, try to obtain from current path the parent path.
// if path is /resource/1/test and pattern from pathConfig is /resource/{id}/*, parent path is /resource/1
// this logic allows to match sub resources of a resource instance (/resource/1) to the parent resource,
// so any permission granted to parent also applies to sub resources
if (search.isEmpty()) {
search = resource.findByUri(buildUriFromTemplate(originalConfig.getPath(), path, true));
}
if (!search.isEmpty()) {
ResourceRepresentation targetResource = search.get(0);
PathConfig config = PathConfig.createPathConfig(targetResource);
config.setScopes(originalConfig.getScopes());
config.setMethods(originalConfig.getMethods());
config.setParentConfig(originalConfig);
config.setEnforcementMode(originalConfig.getEnforcementMode());
config.setClaimInformationPointConfig(originalConfig.getClaimInformationPointConfig());
return config;
}
}
return null;
}
public void removeFromCache(String pathConfig) {
pathCache.remove(pathConfig);
}
};
return pathConfig;
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2018 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.cip;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
import org.keycloak.adapters.authorization.util.PlaceHolders;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ClaimsInformationPointProvider implements ClaimInformationPointProvider {
private final Map<String, Object> config;
public ClaimsInformationPointProvider(Map<String, Object> config) {
this.config = config;
}
@Override
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
Map<String, List<String>> claims = new HashMap<>();
for (Entry<String, Object> configEntry : config.entrySet()) {
String claimName = configEntry.getKey();
Object claimValue = configEntry.getValue();
List<String> values = new ArrayList<>();
if (claimValue instanceof String) {
values = getValues(claimValue.toString(), httpFacade);
} else if (claimValue instanceof Collection) {
Iterator iterator = Collection.class.cast(claimValue).iterator();
while (iterator.hasNext()) {
List<String> resolvedValues = getValues(iterator.next().toString(), httpFacade);
if (!resolvedValues.isEmpty()) {
values.addAll(resolvedValues);
}
}
}
if (!values.isEmpty()) {
claims.put(claimName, values);
}
}
return claims;
}
private List<String> getValues(String value, HttpFacade httpFacade) {
return PlaceHolders.resolve(value, httpFacade);
}
}
@@ -1,43 +0,0 @@
/*
* Copyright 2018 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.cip;
import java.util.Map;
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
import org.keycloak.adapters.authorization.PolicyEnforcer;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class ClaimsInformationPointProviderFactory implements ClaimInformationPointProviderFactory<ClaimsInformationPointProvider> {
@Override
public String getName() {
return "claims";
}
@Override
public void init(PolicyEnforcer policyEnforcer) {
}
@Override
public ClaimsInformationPointProvider create(Map<String, Object> config) {
return new ClaimsInformationPointProvider(config);
}
}
@@ -1,210 +0,0 @@
/*
* Copyright 2018 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.cip;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.util.EntityUtils;
import org.keycloak.adapters.authorization.ClaimInformationPointProvider;
import org.keycloak.adapters.authorization.PolicyEnforcer;
import org.keycloak.adapters.authorization.util.JsonUtils;
import org.keycloak.adapters.authorization.util.PlaceHolders;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class HttpClaimInformationPointProvider implements ClaimInformationPointProvider {
private final Map<String, Object> config;
private final HttpClient httpClient;
public HttpClaimInformationPointProvider(Map<String, Object> config, PolicyEnforcer policyEnforcer) {
this.config = config;
this.httpClient = policyEnforcer.getDeployment().getClient();
}
@Override
public Map<String, List<String>> resolve(HttpFacade httpFacade) {
try {
InputStream responseStream = executeRequest(httpFacade);
try (InputStream inputStream = new BufferedInputStream(responseStream)) {
JsonNode jsonNode = JsonSerialization.mapper.readTree(inputStream);
Map<String, List<String>> claims = new HashMap<>();
Map<String, Object> claimsDef = (Map<String, Object>) config.get("claims");
if (claimsDef == null) {
Iterator<String> nodeNames = jsonNode.fieldNames();
while (nodeNames.hasNext()) {
String nodeName = nodeNames.next();
claims.put(nodeName, JsonUtils.getValues(jsonNode.get(nodeName)));
}
} else {
for (Entry<String, Object> claimDef : claimsDef.entrySet()) {
List<String> jsonPaths = new ArrayList<>();
if (claimDef.getValue() instanceof Collection) {
jsonPaths.addAll(Collection.class.cast(claimDef.getValue()));
} else {
jsonPaths.add(claimDef.getValue().toString());
}
List<String> claimValues = new ArrayList<>();
for (String path : jsonPaths) {
claimValues.addAll(JsonUtils.getValues(jsonNode, path));
}
claims.put(claimDef.getKey(), claimValues);
}
}
return claims;
}
} catch (IOException cause) {
throw new RuntimeException("Could not obtain claims from http claim information point [" + config.get("url") + "] response", cause);
}
}
private InputStream executeRequest(HttpFacade httpFacade) {
String method = config.get("method").toString();
if (method == null) {
method = "GET";
}
RequestBuilder builder = null;
if ("GET".equalsIgnoreCase(method)) {
builder = RequestBuilder.get();
} else {
builder = RequestBuilder.post();
}
builder.setUri(config.get("url").toString());
byte[] bytes = new byte[0];
try {
setParameters(builder, httpFacade);
if (config.containsKey("headers")) {
setHeaders(builder, httpFacade);
}
HttpResponse response = httpClient.execute(builder.build());
HttpEntity entity = response.getEntity();
if (entity != null) {
bytes = EntityUtils.toByteArray(entity);
}
StatusLine statusLine = response.getStatusLine();
int statusCode = statusLine.getStatusCode();
if (statusCode < 200 || statusCode >= 300) {
throw new HttpResponseException("Unexpected response from server: " + statusCode + " / " + statusLine.getReasonPhrase(), statusCode, statusLine.getReasonPhrase(), bytes);
}
return new ByteArrayInputStream(bytes);
} catch (Exception cause) {
try {
throw new RuntimeException("Error executing http method [" + builder + "]. Response : " + StreamUtil.readString(new ByteArrayInputStream(bytes), Charset.forName("UTF-8")), cause);
} catch (Exception e) {
throw new RuntimeException("Error executing http method [" + builder + "]", cause);
}
}
}
private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
Object headersDef = config.get("headers");
if (headersDef != null) {
Map<String, Object> headers = Map.class.cast(headersDef);
for (Entry<String, Object> header : headers.entrySet()) {
Object value = header.getValue();
List<String> headerValues = new ArrayList<>();
if (value instanceof Collection) {
Collection values = Collection.class.cast(value);
Iterator iterator = values.iterator();
while (iterator.hasNext()) {
headerValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
}
} else {
headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
}
for (String headerValue : headerValues) {
builder.addHeader(header.getKey(), headerValue);
}
}
}
}
private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
Object config = this.config.get("parameters");
if (config != null) {
Map<String, Object> paramsDef = Map.class.cast(config);
for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
Object value = paramDef.getValue();
List<String> paramValues = new ArrayList<>();
if (value instanceof Collection) {
Collection values = Collection.class.cast(value);
Iterator iterator = values.iterator();
while (iterator.hasNext()) {
paramValues.addAll(PlaceHolders.resolve(iterator.next().toString(), httpFacade));
}
} else {
paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
}
for (String paramValue : paramValues) {
builder.addParameter(paramDef.getKey(), paramValue);
}
}
}
}
}
@@ -1,45 +0,0 @@
/*
* Copyright 2018 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.cip;
import java.util.Map;
import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory;
import org.keycloak.adapters.authorization.PolicyEnforcer;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class HttpClaimInformationPointProviderFactory implements ClaimInformationPointProviderFactory<HttpClaimInformationPointProvider> {
private PolicyEnforcer policyEnforcer;
@Override
public String getName() {
return "http";
}
@Override
public void init(PolicyEnforcer policyEnforcer) {
this.policyEnforcer = policyEnforcer;
}
@Override
public HttpClaimInformationPointProvider create(Map<String, Object> config) {
return new HttpClaimInformationPointProvider(config, policyEnforcer);
}
}
@@ -1,58 +0,0 @@
/*
* Copyright 2018 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.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class JsonUtils {
public static List<String> getValues(JsonNode jsonNode, String path) {
return getValues(jsonNode.at(path));
}
public static List<String> getValues(JsonNode jsonNode) {
List<String> values = new ArrayList<>();
if (jsonNode.isArray()) {
Iterator<JsonNode> iterator = jsonNode.iterator();
while (iterator.hasNext()) {
String value = iterator.next().textValue();
if (value != null) {
values.add(value);
}
}
} else {
String value = jsonNode.textValue();
if (value != null) {
values.add(value);
}
}
return values;
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2018 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.util;
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class KeycloakSecurityContextPlaceHolderResolver implements PlaceHolderResolver {
public static final String NAME = "keycloak";
@Override
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();
if (securityContext == null) {
return null;
}
if (source.endsWith("access_token")) {
return Arrays.asList(securityContext.getTokenString());
}
if (source.endsWith("id_token")) {
return Arrays.asList(securityContext.getIdTokenString());
}
JsonNode jsonNode;
if (source.startsWith("access_token[")) {
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
} else if (source.startsWith("id_token[")) {
jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
} else {
throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
}
return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
}
}
@@ -1,30 +0,0 @@
/*
* Copyright 2018 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.util;
import java.util.List;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public interface PlaceHolderResolver {
List<String> resolve(String placeHolder, HttpFacade httpFacade);
}
@@ -1,107 +0,0 @@
/*
* Copyright 2018 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.util;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class PlaceHolders {
private static Map<String, PlaceHolderResolver> resolvers = new HashMap<>();
static {
resolvers.put(RequestPlaceHolderResolver.NAME, new RequestPlaceHolderResolver());
resolvers.put(KeycloakSecurityContextPlaceHolderResolver.NAME, new KeycloakSecurityContextPlaceHolderResolver());
}
private static Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(.+?)\\}");
private static Pattern PLACEHOLDER_PARAM_PATTERN = Pattern.compile("\\[(.+?)\\]");
public static List<String> resolve(String value, HttpFacade httpFacade) {
Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);
if (!placeHolders.isEmpty()) {
value = formatPlaceHolder(value);
for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
List<String> values = entry.getValue();
if (values.isEmpty() || values.size() > 1) {
return values;
}
value = value.replaceAll(entry.getKey(), values.get(0)).trim();
}
}
return Arrays.asList(value);
}
static String getParameter(String source, String messageIfNotFound) {
Matcher matcher = PLACEHOLDER_PARAM_PATTERN.matcher(source);
while (matcher.find()) {
return matcher.group(1).replaceAll("'", "");
}
if (messageIfNotFound != null) {
throw new RuntimeException(messageIfNotFound);
}
return null;
}
private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
Map<String, List<String>> placeHolders = new HashMap<>();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);
while (matcher.find()) {
String placeHolder = matcher.group(1);
int resolverNameIdx = placeHolder.indexOf('.');
if (resolverNameIdx == -1) {
throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
}
PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));
if (resolver != null) {
List<String> resolved = resolver.resolve(placeHolder, httpFacade);
if (resolved != null) {
placeHolders.put(formatPlaceHolder(placeHolder), resolved);
}
}
}
return placeHolders;
}
private static String formatPlaceHolder(String placeHolder) {
return placeHolder.replaceAll("\\{", "").replace("}", "").replace("[", "").replace("]", "").replace("[", "").replace("]", "");
}
}
@@ -1,175 +0,0 @@
/*
* Copyright 2018 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.util;
import static org.keycloak.adapters.authorization.util.PlaceHolders.getParameter;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.HttpFacade.Cookie;
import org.keycloak.adapters.spi.HttpFacade.Request;
import org.keycloak.util.JsonSerialization;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class RequestPlaceHolderResolver implements PlaceHolderResolver {
static String NAME = "request";
@Override
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
Request request = httpFacade.getRequest();
if (source.startsWith("parameter")) {
String parameterName = getParameter(source, "Could not obtain parameter name from placeholder [" + source + "]");
String parameterValue = request.getQueryParamValue(parameterName);
if (parameterValue == null) {
parameterValue = request.getFirstParam(parameterName);
}
if (parameterValue != null) {
return Arrays.asList(parameterValue);
}
} else if (source.startsWith("header")) {
String headerName = getParameter(source, "Could not obtain header name from placeholder [" + source + "]");
List<String> headerValue = request.getHeaders(headerName);
if (headerValue != null) {
return headerValue;
}
} else if (source.startsWith("cookie")) {
String cookieName = getParameter(source, "Could not obtain cookie name from placeholder [" + source + "]");
Cookie cookieValue = request.getCookie(cookieName);
if (cookieValue != null) {
return Arrays.asList(cookieValue.getValue());
}
} else if (source.startsWith("remoteAddr")) {
String value = request.getRemoteAddr();
if (value != null) {
return Arrays.asList(value);
}
} else if (source.startsWith("method")) {
String value = request.getMethod();
if (value != null) {
return Arrays.asList(value);
}
} else if (source.startsWith("uri")) {
String value = request.getURI();
if (value != null) {
return Arrays.asList(value);
}
} else if (source.startsWith("relativePath")) {
String value = request.getRelativePath();
if (value != null) {
return Arrays.asList(value);
}
} else if (source.startsWith("secure")) {
return Arrays.asList(String.valueOf(request.isSecure()));
} else if (source.startsWith("body")) {
String contentType = request.getHeader("Content-Type");
if (contentType == null) {
contentType = "";
}
InputStream body = request.getInputStream(true);
try {
if (body == null || body.available() == 0) {
return Collections.emptyList();
}
} catch (IOException cause) {
throw new RuntimeException("Failed to check available bytes in request input stream", cause);
}
if (body.markSupported()) {
body.mark(0);
}
List<String> values = new ArrayList<>();
try {
switch (contentType) {
case "application/json":
try {
JsonNode jsonNode = JsonSerialization.mapper.readTree(new BufferedInputStream(body) {
@Override
public void close() {
// we can't close the stream because it may be used later by the application
}
});
String path = getParameter(source, null);
if (path == null) {
values.addAll(JsonUtils.getValues(jsonNode));
} else {
values.addAll(JsonUtils.getValues(jsonNode, path));
}
} catch (IOException cause) {
throw new RuntimeException("Could not extract claim from request JSON body", cause);
}
break;
default:
StringBuilder value = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(body));
try {
int ch;
while ((ch = reader.read()) != -1) {
value.append((char) ch);
}
} catch (IOException cause) {
throw new RuntimeException("Could not extract claim from request body", cause);
}
values.add(value.toString());
}
} finally {
if (body.markSupported()) {
try {
body.reset();
} catch (IOException cause) {
throw new RuntimeException("Failed to reset request input stream", cause);
}
}
}
return values;
}
return Collections.emptyList();
}
}
@@ -37,12 +37,8 @@ import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.HashSet;
import java.util.Map;
@@ -57,7 +53,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file";
public static final String ROLE_PRINCIPAL_CLASS_OPTION = "role-principal-class";
public static final String PROFILE_RESOURCE = "profile:";
protected Subject subject;
protected CallbackHandler callbackHandler;
protected Auth auth;
@@ -88,27 +84,14 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
protected KeycloakDeployment resolveDeployment(String keycloakConfigFile) {
try {
InputStream is = null;
if (keycloakConfigFile.startsWith(PROFILE_RESOURCE)) {
try {
is = new URL(keycloakConfigFile).openStream();
} catch (MalformedURLException mfue) {
throw new RuntimeException(mfue);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
is = FindFile.findFile(keycloakConfigFile);
}
InputStream is = FindFile.findFile(keycloakConfigFile);
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
return kd;
} catch (RuntimeException e) {
getLogger().debug("Unable to find or parse file " + keycloakConfigFile + " due to " + e.getMessage(), e);
throw e;
}
}
@Override
public boolean login() throws LoginException {
@@ -81,7 +81,7 @@ public class JWKPublicKeyLocator implements PublicKeyLocator {
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
if (lastRequestTime + publicKeyCacheTtl > currentTime && kid != null) {
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
return currentKeys.get(kid);
} else {
return null;
@@ -16,5 +16,4 @@
#
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
@@ -1,19 +0,0 @@
#
# * Copyright 2018 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.
#
org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory
org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory
@@ -21,7 +21,6 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider;
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
import org.keycloak.common.enums.RelativeUrlsUsed;
@@ -30,13 +29,10 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.enums.TokenStore;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* @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 {
@@ -57,12 +53,10 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertEquals("X-Custom3, X-Custom4", deployment.getCorsExposedHeaders());
assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken());
assertFalse(deployment.isOAuthQueryParameterEnabled());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
@@ -76,7 +70,6 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(10, deployment.getTokenMinimumTimeToLive());
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
assertEquals(120, deployment.getPublicKeyCacheTtl());
assertEquals("/api/$1", deployment.getRedirectRewriteRules().get("^/wsmaster/api/(.*)$"));
}
@Test
@@ -95,9 +88,4 @@ public class KeycloakDeploymentBuilderTest {
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
@Test
public void loadSecretJwtCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-secret-jwt.json"));
assertEquals(JWTClientSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
}
@@ -1,68 +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 org.keycloak.representations.adapters.config.AdapterConfig;
import static org.junit.Assert.assertEquals;
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());
}
@Test
public void stripDefaultPorts() {
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
keycloakDeployment.setRealm("test");
AdapterConfig config = new AdapterConfig();
config.setAuthServerUrl("http://localhost:80/auth");
keycloakDeployment.setAuthServerBaseUrl(config);
assertEquals("http://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
config.setAuthServerUrl("https://localhost:443/auth");
keycloakDeployment.setAuthServerBaseUrl(config);
assertEquals("https://localhost/auth", keycloakDeployment.getAuthServerBaseUrl());
}
}
@@ -1,12 +0,0 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"ssl-required": "external",
"credentials": {
"secret-jwt": {
"secret": "234234-234234-234234"
}
}
}
@@ -9,7 +9,6 @@
"cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2",
"cors-exposed-headers": "X-Custom3, X-Custom4",
"bearer-only": true,
"public-client": true,
"enable-basic-auth": true,
@@ -32,9 +31,5 @@
"principal-attribute": "email",
"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"
}
"public-key-cache-ttl": 120
}
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -1,13 +0,0 @@
package org.keycloak.adapters.jbossweb;
import org.apache.catalina.Container;
import org.apache.catalina.Valve;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve;
public class AuthenticatedActionsValve extends AbstractAuthenticatedActionsValve {
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
super(deploymentContext, next, container);
}
}
@@ -17,15 +17,11 @@
package org.keycloak.adapters.jbossweb;
import org.apache.catalina.Container;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Valve;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve;
import org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
@@ -60,6 +56,7 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
super.start();
}
public void logout(Request request) {
logoutInternal(request);
}
@@ -68,9 +65,4 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
protected GenericPrincipalFactory createPrincipalFactory() {
return new JBossWebPrincipalFactory();
}
@Override
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
return new AuthenticatedActionsValve(deploymentContext, next, container);
}
}
+3 -1
View File
@@ -21,7 +21,7 @@
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -88,6 +88,7 @@
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
@@ -103,6 +104,7 @@ projects that depend on this project.-->
<dependency>
<groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency>
<dependency>
+1 -13
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
@@ -30,18 +30,6 @@
<artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-parent</artifactId>
<version>${jboss.as.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>as7-adapter-spi</module>
<module>as7-adapter</module>
-144
View File
@@ -1,144 +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-fuse7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-camel-undertow</artifactId>
<name>Keycloak Fuse 7.0 Adapter - Camel + Undertow</name>
<packaging>bundle</packaging>
<properties>
<keycloak.osgi.export>
org.keycloak.adapters.camel.undertow;version="${project.version}"
</keycloak.osgi.export>
<keycloak.osgi.import>
org.keycloak.*;version="${project.version}",
org.apache.camel.*,
org.apache.camel.component.undertow,
io.undertow.*,
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-runtime</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-undertow</artifactId>
<version>2.21.0</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>2.21.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- 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-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>
<Export-Service>org.apache.camel.spi.ComponentResolver;component=undertow-keycloak</Export-Service>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,187 +0,0 @@
/*
* Copyright 2018 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.camel.undertow;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.component.undertow.RestUndertowHttpBinding;
import org.apache.camel.component.undertow.UndertowComponent;
import org.apache.camel.component.undertow.UndertowEndpoint;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.HostUtils;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.URISupport;
/**
*
* @author hmlnarik
*/
public class UndertowKeycloakComponent extends UndertowComponent {
public UndertowKeycloakComponent() {
}
public UndertowKeycloakComponent(CamelContext context) {
super(context);
}
@Override
protected UndertowEndpoint createEndpointInstance(URI endpointUri, UndertowComponent component) throws URISyntaxException {
return new UndertowKeycloakEndpoint(endpointUri.toString(), component);
}
// TODO: uncomment line below after backport of https://issues.apache.org/jira/browse/CAMEL-12514 into fuse
// @Override
protected String getComponentName() {
return "undertow-keycloak";
}
// TODO: remove all below this line after backport of https://issues.apache.org/jira/browse/CAMEL-12514 into fuse
@Override
public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
String consumes, String produces, RestConfiguration configuration, Map<String, Object> parameters) throws Exception {
return doCreateConsumer(camelContext, processor, verb, basePath, uriTemplate, consumes, produces, configuration, parameters, false);
}
@Override
public Consumer createApiConsumer(CamelContext camelContext, Processor processor, String contextPath,
RestConfiguration configuration, Map<String, Object> parameters) throws Exception {
// reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
return doCreateConsumer(camelContext, processor, "GET", contextPath, null, null, null, configuration, parameters, true);
}
Consumer doCreateConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
String consumes, String produces, RestConfiguration configuration, Map<String, Object> parameters, boolean api) throws Exception {
String path = basePath;
if (uriTemplate != null) {
// make sure to avoid double slashes
if (uriTemplate.startsWith("/")) {
path = path + uriTemplate;
} else {
path = path + "/" + uriTemplate;
}
}
path = FileUtil.stripLeadingSeparator(path);
String scheme = "http";
String host = "";
int port = 0;
RestConfiguration config = configuration;
if (config == null) {
config = camelContext.getRestConfiguration(getComponentName(), true);
}
if (config.getScheme() != null) {
scheme = config.getScheme();
}
if (config.getHost() != null) {
host = config.getHost();
}
int num = config.getPort();
if (num > 0) {
port = num;
}
// prefix path with context-path if configured in rest-dsl configuration
String contextPath = config.getContextPath();
if (ObjectHelper.isNotEmpty(contextPath)) {
contextPath = FileUtil.stripTrailingSeparator(contextPath);
contextPath = FileUtil.stripLeadingSeparator(contextPath);
if (ObjectHelper.isNotEmpty(contextPath)) {
path = contextPath + "/" + path;
}
}
// if no explicit hostname set then resolve the hostname
if (ObjectHelper.isEmpty(host)) {
if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) {
host = "0.0.0.0";
} else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) {
host = HostUtils.getLocalHostName();
} else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) {
host = HostUtils.getLocalIp();
}
}
Map<String, Object> map = new HashMap<String, Object>();
// build query string, and append any endpoint configuration properties
if (config.getComponent() == null || config.getComponent().equals(getComponentName())) {
// setup endpoint options
if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) {
map.putAll(config.getEndpointProperties());
}
}
boolean explicitOptions = true;
// must use upper case for restrict
String restrict = verb.toUpperCase(Locale.US);
// allow OPTIONS in rest-dsl to allow clients to call the API and have responses with ALLOW headers
if (!restrict.contains("OPTIONS")) {
restrict += ",OPTIONS";
// this is not an explicit OPTIONS path in the rest-dsl
explicitOptions = false;
}
boolean cors = config.isEnableCORS();
if (cors) {
// allow HTTP Options as we want to handle CORS in rest-dsl
map.put("optionsEnabled", "true");
} else if (explicitOptions) {
// the rest-dsl is using OPTIONS
map.put("optionsEnabled", "true");
}
String query = URISupport.createQueryString(map);
String url;
if (api) {
url = getComponentName() + ":%s://%s:%s/%s?matchOnUriPrefix=true&httpMethodRestrict=%s";
} else {
url = getComponentName() + ":%s://%s:%s/%s?matchOnUriPrefix=false&httpMethodRestrict=%s";
}
// get the endpoint
url = String.format(url, scheme, host, port, path, restrict);
if (!query.isEmpty()) {
url = url + "&" + query;
}
UndertowEndpoint endpoint = camelContext.getEndpoint(url, UndertowEndpoint.class);
setProperties(camelContext, endpoint, parameters);
if (!map.containsKey("undertowHttpBinding")) {
// use the rest binding, if not using a custom http binding
endpoint.setUndertowHttpBinding(new RestUndertowHttpBinding());
}
// configure consumer properties
Consumer consumer = endpoint.createConsumer(processor);
if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
setProperties(camelContext, consumer, config.getConsumerProperties());
}
return consumer;
}
}
@@ -1,219 +0,0 @@
/*
* Copyright 2018 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.camel.undertow;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RequestAuthenticator;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
import org.keycloak.adapters.undertow.OIDCUndertowHttpFacade;
import org.keycloak.adapters.undertow.SessionManagementBridge;
import org.keycloak.adapters.undertow.UndertowCookieTokenStore;
import org.keycloak.adapters.undertow.UndertowRequestAuthenticator;
import org.keycloak.adapters.undertow.UndertowSessionTokenStore;
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
import org.keycloak.enums.TokenStore;
import io.undertow.security.api.SecurityContext;
import io.undertow.security.idm.Account;
import io.undertow.security.idm.Credential;
import io.undertow.security.idm.IdentityManager;
import io.undertow.security.impl.SecurityContextImpl;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.session.InMemorySessionManager;
import io.undertow.server.session.SessionManager;
import io.undertow.util.AttachmentKey;
import io.undertow.util.StatusCodes;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.camel.Processor;
import org.apache.camel.component.undertow.UndertowConsumer;
/**
*
* @author hmlnarik
*/
public class UndertowKeycloakConsumer extends UndertowConsumer {
private static final Logger LOG = Logger.getLogger(UndertowKeycloakConsumer.class.getName());
public static final AttachmentKey<KeycloakPrincipal> KEYCLOAK_PRINCIPAL_KEY = AttachmentKey.create(KeycloakPrincipal.class);
private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() {
@Override
public Account verify(Account account) {
return account;
}
@Override
public Account verify(String id, Credential credential) {
throw new IllegalStateException("Should never be called in Keycloak flow");
}
@Override
public Account verify(Credential credential) {
throw new IllegalStateException("Should never be called in Keycloak flow");
}
};
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
protected final AdapterDeploymentContext deploymentContext;
protected final SessionManager sessionManager;
protected final List<String> allowedRoles;
private final int confidentialPort;
private final Pattern skipPattern;
public UndertowKeycloakConsumer(UndertowKeycloakEndpoint endpoint, Processor processor,
AdapterDeploymentContext deploymentContext, Pattern skipPattern, List<String> allowedRoles, int confidentialPort) {
super(endpoint, processor);
this.sessionManager = new InMemorySessionManager(endpoint.getEndpointUri());
this.deploymentContext = deploymentContext;
this.skipPattern = skipPattern;
this.confidentialPort = confidentialPort;
this.allowedRoles = allowedRoles == null ? Collections.<String>emptyList() : allowedRoles;
}
public int getConfidentialPort() {
return confidentialPort;
}
@Override
public void handleRequest(HttpServerExchange httpExchange) throws Exception {
if (shouldSkip(httpExchange.getRequestPath())) {
super.handleRequest(httpExchange);
return;
}
//perform only non-blocking operation on exchange
if (httpExchange.isInIoThread()) {
httpExchange.dispatch(this);
return;
}
OIDCUndertowHttpFacade facade = new OIDCUndertowHttpFacade(httpExchange);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
LOG.fine("deployment not configured");
return;
}
LOG.fine("executing PreAuthActionsHandler");
SessionManagementBridge bridge = new SessionManagementBridge(userSessionManagement, sessionManager);
PreAuthActionsHandler preAuth = new PreAuthActionsHandler(bridge, deploymentContext, facade);
if (preAuth.handleRequest()) return;
SecurityContext securityContext = httpExchange.getSecurityContext();
if (securityContext == null) {
securityContext = new SecurityContextImpl(httpExchange, IDENTITY_MANAGER);
}
AdapterTokenStore tokenStore = getTokenStore(httpExchange, facade, deployment, securityContext);
tokenStore.checkCurrentToken();
LOG.fine("executing AuthenticatedActionsHandler");
RequestAuthenticator authenticator = new UndertowRequestAuthenticator(facade, deployment, confidentialPort, securityContext, httpExchange, tokenStore);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
LOG.fine("AUTHENTICATED");
if (httpExchange.isResponseComplete()) {
return;
}
AuthenticatedActionsHandler actions = new AuthenticatedActionsHandler(deployment, facade);
if (actions.handledRequest()) {
return;
} else {
final Account authenticatedAccount = securityContext.getAuthenticatedAccount();
if (authenticatedAccount instanceof KeycloakUndertowAccount) {
final KeycloakUndertowAccount kua = (KeycloakUndertowAccount) authenticatedAccount;
httpExchange.putAttachment(KEYCLOAK_PRINCIPAL_KEY, (KeycloakPrincipal) kua.getPrincipal());
}
Set<String> roles = Optional
.ofNullable(authenticatedAccount.getRoles())
.orElse((Set<String>) Collections.EMPTY_SET);
LOG.log(Level.FINE, "Allowed roles: {0}, current roles: {1}", new Object[] {allowedRoles, roles});
if (isRoleAllowed(roles, httpExchange)) {
super.handleRequest(httpExchange);
} else {
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
}
return;
}
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
LOG.fine("challenge");
challenge.challenge(facade);
return;
}
httpExchange.setStatusCode(StatusCodes.FORBIDDEN);
}
public boolean isRoleAllowed(Set<String> roles, HttpServerExchange httpExchange) throws Exception {
for (String role : allowedRoles) {
if (roles.contains(role)) {
return true;
}
}
return false;
}
protected AdapterTokenStore getTokenStore(HttpServerExchange exchange, HttpFacade facade, KeycloakDeployment deployment, SecurityContext securityContext) {
if (deployment.getTokenStore() == TokenStore.SESSION) {
return new UndertowSessionTokenStore(exchange, deployment, userSessionManagement, securityContext);
} else {
return new UndertowCookieTokenStore(facade, deployment, securityContext);
}
}
private boolean shouldSkip(String requestPath) {
return skipPattern != null && skipPattern.matcher(requestPath).matches();
}
}
@@ -1,152 +0,0 @@
/*
* Copyright 2018 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.camel.undertow;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.representations.adapters.config.AdapterConfig;
import io.undertow.server.HttpServerExchange;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.camel.Consumer;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.component.undertow.UndertowComponent;
import org.apache.camel.component.undertow.UndertowEndpoint;
import static org.keycloak.adapters.camel.undertow.UndertowKeycloakConsumer.KEYCLOAK_PRINCIPAL_KEY;
/**
*
* @author hmlnarik
*/
public class UndertowKeycloakEndpoint extends UndertowEndpoint {
private static final Logger LOG = Logger.getLogger(UndertowKeycloakEndpoint.class.getName());
private KeycloakConfigResolver configResolver;
private AdapterConfig adapterConfig;
private String skipPattern;
private List<String> allowedRoles = Collections.emptyList();
private int confidentialPort = 8443;
public UndertowKeycloakEndpoint(String uri, UndertowComponent component) {
super(uri, component);
}
public AdapterConfig getAdapterConfig() {
return adapterConfig;
}
public void setAdapterConfig(AdapterConfig adapterConfig) {
LOG.info("adapterConfig");
this.adapterConfig = adapterConfig;
}
public String getSkipPattern() {
return skipPattern;
}
public void setSkipPattern(String skipPattern) {
this.skipPattern = skipPattern;
}
public List<String> getAllowedRoles() {
return allowedRoles;
}
public void setAllowedRoles(List<String> allowedRoles) {
this.allowedRoles = allowedRoles;
}
public void setAllowedRoles(String allowedRoles) {
this.allowedRoles = allowedRoles == null ? null : Arrays.asList(allowedRoles.split("\\s*,\\s*"));
}
public int getConfidentialPort() {
return confidentialPort;
}
public void setConfidentialPort(int confidentialPort) {
this.confidentialPort = confidentialPort;
}
public KeycloakConfigResolver getConfigResolver() {
return configResolver;
}
public void setConfigResolver(KeycloakConfigResolver configResolver) {
this.configResolver = configResolver;
}
@Override
public Consumer createConsumer(Processor processor) throws Exception {
return new UndertowKeycloakConsumer(this, processor, getDeploymentContext(), getSkipPatternAsPattern(), computeAllowedRoles(), this.confidentialPort);
}
public List<String> computeAllowedRoles() {
List<String> res = this.allowedRoles == null ? Collections.<String>emptyList() : this.allowedRoles;
if (res.isEmpty()) {
LOG.warning("No roles were configured, Keycloak will deny every request");
}
LOG.log(Level.FINE, "Allowed roles: {0}", res);
return res;
}
@Override
public Exchange createExchange(HttpServerExchange httpExchange) throws Exception {
final Exchange res = super.createExchange(httpExchange);
KeycloakPrincipal principal = httpExchange.getAttachment(KEYCLOAK_PRINCIPAL_KEY);
LOG.log(Level.FINE, "principal: {0}", principal);
if (principal != null) {
res.setProperty(KeycloakPrincipal.class.getName(), principal);
}
return res;
}
private AdapterDeploymentContext getDeploymentContext() {
if (configResolver != null) {
LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass());
return new AdapterDeploymentContext(configResolver);
} else if (adapterConfig != null) {
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
return new AdapterDeploymentContext(kd);
}
LOG.warning("Adapter is unconfigured, Keycloak will deny every request");
return new AdapterDeploymentContext();
}
private Pattern getSkipPatternAsPattern() {
return skipPattern == null
? null
: Pattern.compile(skipPattern, Pattern.DOTALL);
}
}
@@ -1 +0,0 @@
class=org.keycloak.adapters.camel.undertow.UndertowKeycloakComponent
-131
View File
@@ -1,131 +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-fuse7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-pax-web-jetty94</artifactId>
<name>Keycloak Fuse 7.0 Adapter - Jetty 9.4</name>
<packaging>bundle</packaging>
<properties>
<keycloak.osgi.export>
org.keycloak.adapters.osgi.jetty94.*;version="${project.version}"
</keycloak.osgi.export>
<keycloak.osgi.import>
!org.keycloak.adapters.osgi.jetty94,
org.ops4j.pax.web.*;version="[7.1,9)",
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-jetty</keycloak.osgi.fragment>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty94-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- 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-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>
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,41 +0,0 @@
/*
* Copyright 2018 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.osgi.jetty94;
import org.ops4j.pax.web.service.AuthenticatorService;
/**
*
* @author hmlnarik
*/
public class KeycloakAuthenticatorService implements AuthenticatorService {
@Override
public <T> T getAuthenticatorService(String method, Class<T> iface) {
if (method == null || iface != org.eclipse.jetty.security.Authenticator.class) {
return null;
}
if ("KEYCLOAK".equalsIgnoreCase(method)) {
return iface.cast(new org.keycloak.adapters.jetty.KeycloakJettyAuthenticator());
}
return null;
}
}
@@ -1,280 +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.osgi.jetty94;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.util.security.Constraint;
import org.jboss.logging.Logger;
import org.ops4j.pax.web.service.WebContainer;
import org.ops4j.pax.web.service.spi.model.SecurityConstraintMappingModel;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpContext;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import java.net.URL;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
* and allows to inject custom security constraint for securing resources by Keycloak.
*
* <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which
* is true in karaf/fuse environment</p>
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PaxWebIntegrationService {
protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class);
private BundleContext bundleContext;
private String jettyWebXmlLocation;
private List<Object> constraintMappings;
private ServiceTracker webContainerTracker;
private HttpContext httpContext;
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public String getJettyWebXmlLocation() {
return jettyWebXmlLocation;
}
public void setJettyWebXmlLocation(String jettyWebXmlLocation) {
this.jettyWebXmlLocation = jettyWebXmlLocation;
}
public List<Object> getConstraintMappings() {
return constraintMappings;
}
public void setConstraintMappings(List<Object> constraintMappings) {
this.constraintMappings = constraintMappings;
}
protected ServiceTracker getWebContainerTracker() {
return webContainerTracker;
}
protected HttpContext getHttpContext() {
return httpContext;
}
public void setHttpContext(HttpContext httpContext) {
this.httpContext = httpContext;
}
public void start() {
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
@Override
public Object addingService(ServiceReference reference) {
return addingWebContainerCallback(reference);
}
@Override
public void modifiedService(ServiceReference reference, Object service) {
}
@Override
public void removedService(ServiceReference reference, Object service) {
removingWebContainerCallback(reference);
}
};
webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer);
webContainerTracker.open();
}
public void stop() {
webContainerTracker.remove(webContainerTracker.getServiceReference());
}
protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) {
WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference);
if (httpContext == null) {
httpContext = service.createDefaultHttpContext();
}
addJettyWebXml(service);
if (constraintMappings == null) {
throw new IllegalStateException("constraintMappings was null!");
}
List<ConstraintHandler> handlers = new ArrayList<>();
try {
handlers.add(new JettyConstraintHandler());
} catch (Throwable t) {
// Ignore
}
try {
handlers.add(new PaxWebConstraintHandler());
} catch (Throwable t) {
// Ignore
}
for (Object constraintMapping : constraintMappings) {
boolean handled = false;
for (ConstraintHandler handler : handlers) {
handled |= handler.addConstraintMapping(httpContext, service, constraintMapping);
}
if (!handled) {
log.warnv("Unable to add constraint mapping for constraint of type " + constraintMapping.getClass().toString());
}
}
service.registerLoginConfig("BASIC", "does-not-matter", null, null, httpContext);
return service;
}
protected void addJettyWebXml(WebContainer service) {
String jettyWebXmlLoc;
if (this.jettyWebXmlLocation == null) {
jettyWebXmlLoc = "/WEB-INF/jetty-web.xml";
} else {
jettyWebXmlLoc = this.jettyWebXmlLocation;
}
URL jettyWebXml = bundleContext.getBundle().getResource(jettyWebXmlLoc);
if (jettyWebXml != null) {
log.debug("Found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
service.registerJettyWebXml(jettyWebXml, httpContext);
} else {
log.debug("Not found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
}
}
protected void addConstraintMapping(WebContainer service, SecurityConstraintMappingModel constraintMapping) {
String name = constraintMapping.getConstraintName();
if (name == null) {
name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
}
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getUrl() + ", dataConstraint=" + constraintMapping.getDataConstraint() + ", canAuthenticate="
+ constraintMapping.isAuthentication() + ", roles=" + constraintMapping.getRoles());
service.registerConstraintMapping(name, constraintMapping.getUrl(), constraintMapping.getMapping(), constraintMapping.getDataConstraint(), constraintMapping.isAuthentication(), constraintMapping.getRoles(), httpContext);
}
protected void addConstraintMapping(WebContainer service, ConstraintMapping constraintMapping) {
Constraint constraint = constraintMapping.getConstraint();
String[] roles = constraint.getRoles();
// name property is unavailable on constraint object :/
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
int dataConstraint = constraint.getDataConstraint();
String dataConstraintStr;
switch (dataConstraint) {
case Constraint.DC_UNSET: dataConstraintStr = null; break;
case Constraint.DC_NONE: dataConstraintStr = "NONE"; break;
case Constraint.DC_CONFIDENTIAL: dataConstraintStr = "CONFIDENTIAL"; break;
case Constraint.DC_INTEGRAL: dataConstraintStr = "INTEGRAL"; break;
default:
log.warnv("Unknown data constraint: " + dataConstraint);
dataConstraintStr = "CONFIDENTIAL";
}
List<String> rolesList = Arrays.asList(roles);
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate="
+ constraint.getAuthenticate() + ", roles=" + rolesList);
service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext);
}
protected void removingWebContainerCallback(ServiceReference serviceReference) {
WebContainer service = (WebContainer)bundleContext.getService(serviceReference);
if (service != null) {
service.unregisterLoginConfig(httpContext);
service.unregisterConstraintMapping(httpContext);
}
}
private interface ConstraintHandler {
boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm);
}
private static class PaxWebConstraintHandler implements ConstraintHandler {
public boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm) {
if (cm instanceof SecurityConstraintMappingModel) {
SecurityConstraintMappingModel constraintMapping = (SecurityConstraintMappingModel) cm;
String name = constraintMapping.getConstraintName();
if (name == null) {
name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
}
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getUrl() + ", dataConstraint=" + constraintMapping.getDataConstraint() + ", canAuthenticate="
+ constraintMapping.isAuthentication() + ", roles=" + constraintMapping.getRoles());
service.registerConstraintMapping(name, constraintMapping.getUrl(), constraintMapping.getMapping(), constraintMapping.getDataConstraint(), constraintMapping.isAuthentication(), constraintMapping.getRoles(), httpContext);
return true;
}
return false;
}
}
private static class JettyConstraintHandler implements ConstraintHandler {
public boolean addConstraintMapping(HttpContext httpContext, WebContainer service, Object cm) {
if (cm instanceof ConstraintMapping) {
ConstraintMapping constraintMapping = (ConstraintMapping) cm;
Constraint constraint = constraintMapping.getConstraint();
String[] roles = constraint.getRoles();
// name property is unavailable on constraint object :/
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
int dataConstraint = constraint.getDataConstraint();
String dataConstraintStr;
switch (dataConstraint) {
case Constraint.DC_UNSET:
dataConstraintStr = null;
break;
case Constraint.DC_NONE:
dataConstraintStr = "NONE";
break;
case Constraint.DC_CONFIDENTIAL:
dataConstraintStr = "CONFIDENTIAL";
break;
case Constraint.DC_INTEGRAL:
dataConstraintStr = "INTEGRAL";
break;
default:
log.warnv("Unknown data constraint: " + dataConstraint);
dataConstraintStr = "CONFIDENTIAL";
}
List<String> rolesList = Arrays.asList(roles);
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate="
+ constraint.getAuthenticate() + ", roles=" + rolesList);
service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext);
return true;
}
return false;
}
}
}
@@ -1 +0,0 @@
org.keycloak.adapters.osgi.jetty94.KeycloakAuthenticatorService
-45
View File
@@ -1,45 +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>4.1.0.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-fuse7-integration-pom</artifactId>
<packaging>pom</packaging>
<name>Keycloak Fuse 7.0 Integration</name>
<properties>
<fuse7.version>7.0.1-SNAPSHOT</fuse7.version>
<tomcat.version>8.0.14</tomcat.version>
<jetty9.version>9.4.2.v20170220</jetty9.version>
</properties>
<modules>
<module>camel-undertow</module>
<module>jetty94</module>
<module>tomcat8</module>
<module>undertow</module>
</modules>
</project>
-116
View File
@@ -1,116 +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-fuse7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-pax-web-tomcat8</artifactId>
<name>Keycloak Fuse 7.0 Adapter - Tomcat 8</name>
<packaging>bundle</packaging>
<properties>
<keycloak.osgi.export>
org.keycloak.adapters.osgi.tomcat.*;version="${project.version}"
</keycloak.osgi.export>
<keycloak.osgi.import>
!org.keycloak.adapters.osgi.tomcat,
org.ops4j.pax.web.*;version="[7.1,9)",
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-tomcat</keycloak.osgi.fragment>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat8-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- 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-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>
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,41 +0,0 @@
/*
* Copyright 2018 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.osgi.tomcat;
import org.ops4j.pax.web.service.AuthenticatorService;
/**
*
* @author hmlnarik
*/
public class KeycloakAuthenticatorService implements AuthenticatorService {
@Override
public <T> T getAuthenticatorService(String method, Class<T> iface) {
if (method == null || iface != org.apache.catalina.Valve.class) {
return null;
}
if ("KEYCLOAK".equalsIgnoreCase(method)) {
return iface.cast(new org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve());
}
return null;
}
}
@@ -1 +0,0 @@
org.keycloak.adapters.osgi.tomcat.KeycloakAuthenticatorService
-153
View File
@@ -1,153 +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-fuse7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-pax-web-undertow</artifactId>
<name>Keycloak Fuse 7.0 Adapter - Undertow</name>
<packaging>bundle</packaging>
<properties>
<keycloak.osgi.export>
org.keycloak.adapters.osgi.undertow.*;version="${project.version}"
</keycloak.osgi.export>
<keycloak.osgi.import>
!org.keycloak.adapters.osgi.undertow,
org.keycloak.*;version="${project.version}",
org.keycloak.adapters.osgi.*;version="${project.version}",
org.ops4j.pax.web.*;version="[7.1,9)",
javax.servlet.*;version="[2.5,4)";resolution:=optional,
org.apache.cxf.transport.http;resolution:=optional;version="[3,4)",
org.apache.cxf.transport.http_undertow;resolution:=optional;version="[3,4)",
org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)",
io.undertow.*,
*;resolution:=optional
</keycloak.osgi.import>
<keycloak.osgi.fragment>org.ops4j.pax.web.pax-web-undertow</keycloak.osgi.fragment>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-undertow</artifactId>
<version>${cxf.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-servlet</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-osgi-adapter</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- 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-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>
<Fragment-Host>${keycloak.osgi.fragment}</Fragment-Host>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,174 +0,0 @@
/*
* Copyright 2018 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.osgi.undertow;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.spi.InMemorySessionIdMapper;
import org.keycloak.adapters.spi.SessionIdMapper;
import org.keycloak.adapters.undertow.UndertowAuthenticationMechanism;
import org.keycloak.adapters.undertow.UndertowUserSessionManagement;
import org.keycloak.representations.adapters.config.AdapterConfig;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.handlers.AuthenticationCallHandler;
import io.undertow.security.handlers.AuthenticationConstraintHandler;
import io.undertow.security.handlers.AuthenticationMechanismsHandler;
import io.undertow.security.handlers.SecurityInitialHandler;
import io.undertow.security.idm.Account;
import io.undertow.security.idm.Credential;
import io.undertow.security.idm.IdentityManager;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.cxf.transport.http_undertow.CXFUndertowHttpHandler;
/**
*
* @author hmlnarik
*/
public class CxfKeycloakAuthHandler implements CXFUndertowHttpHandler {
private static final Logger LOG = Logger.getLogger(CxfKeycloakAuthHandler.class.getName());
private static final IdentityManager IDENTITY_MANAGER = new IdentityManager() {
@Override
public Account verify(Account account) {
return account;
}
@Override
public Account verify(String id, Credential credential) {
throw new IllegalStateException("Should never be called in Keycloak flow");
}
@Override
public Account verify(Credential credential) {
throw new IllegalStateException("Should never be called in Keycloak flow");
}
};
private final UndertowUserSessionManagement userSessionManagement = new UndertowUserSessionManagement();
protected final NodesRegistrationManagement nodesRegistrationManagement = new NodesRegistrationManagement();
protected final SessionIdMapper idMapper = new InMemorySessionIdMapper();
private final AtomicReference<HttpHandler> securityHandler = new AtomicReference<>();
private Pattern skipPattern;
private int confidentialPort = 8443;
private HttpHandler next;
private KeycloakConfigResolver configResolver;
private AdapterConfig adapterConfig;
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (shouldSkip(exchange.getRequestPath())) {
next.handleRequest(exchange);
} else {
getSecurityHandler().handleRequest(exchange);
}
}
private HttpHandler getSecurityHandler() {
if (this.securityHandler.get() == null) {
HttpHandler handler = this.next;
handler = new AuthenticationCallHandler(handler);
handler = new AuthenticationConstraintHandler(handler);
AdapterDeploymentContext deploymentContext = buildDeploymentContext();
final List<AuthenticationMechanism> mechanisms
= Collections.<AuthenticationMechanism>singletonList(
new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, confidentialPort, null));
handler = new AuthenticationMechanismsHandler(handler, mechanisms);
this.securityHandler.compareAndSet(null, new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, IDENTITY_MANAGER, "KEYCLOAK", handler));
}
return this.securityHandler.get();
}
private AdapterDeploymentContext buildDeploymentContext() {
if (configResolver != null) {
LOG.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolver.getClass());
return new AdapterDeploymentContext(configResolver);
} else if (adapterConfig != null) {
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
return new AdapterDeploymentContext(kd);
}
LOG.warning("Adapter is unconfigured, Keycloak will deny every request");
return new AdapterDeploymentContext();
}
@Override
public void setNext(HttpHandler nextHandler) {
this.next = nextHandler;
}
private boolean shouldSkip(String requestPath) {
return skipPattern != null && skipPattern.matcher(requestPath).matches();
}
public KeycloakConfigResolver getConfigResolver() {
return configResolver;
}
public void setConfigResolver(KeycloakConfigResolver configResolver) {
this.configResolver = configResolver;
}
public int getConfidentialPort() {
return confidentialPort;
}
public void setConfidentialPort(int confidentialPort) {
this.confidentialPort = confidentialPort;
}
public AdapterConfig getAdapterConfig() {
return adapterConfig;
}
public void setAdapterConfig(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
public String getSkipPattern() {
return skipPattern.pattern();
}
public void setSkipPattern(String skipPattern) {
this.skipPattern = Pattern.compile(skipPattern, Pattern.DOTALL);
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2018 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.osgi.undertow;
import org.ops4j.pax.web.service.AuthenticatorService;
/**
*
* @author hmlnarik
*/
public class KeycloakAuthenticatorService implements AuthenticatorService {
@Override
public <T> T getAuthenticatorService(String method, Class<T> iface) {
if (method == null || iface != io.undertow.servlet.ServletExtension.class) {
return null;
}
if ("KEYCLOAK".equalsIgnoreCase(method)) {
return iface.cast(new org.keycloak.adapters.undertow.KeycloakServletExtension());
}
return null;
}
}
@@ -1,142 +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.osgi.undertow;
import org.keycloak.adapters.osgi.PaxWebSecurityConstraintMapping;
import org.jboss.logging.Logger;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpContext;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import java.util.List;
/**
* Integration with pax-web, which allows to inject custom security constraint for securing resources by Keycloak.
*
* <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which
* is true in karaf/fuse environment</p>
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PaxWebIntegrationService {
protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class);
private BundleContext bundleContext;
private List<PaxWebSecurityConstraintMapping> constraintMappings;
private ServiceTracker webContainerTracker;
private HttpContext httpContext;
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public List<PaxWebSecurityConstraintMapping> getConstraintMappings() {
return constraintMappings;
}
public void setConstraintMappings(List<PaxWebSecurityConstraintMapping> constraintMappings) {
this.constraintMappings = constraintMappings;
}
protected ServiceTracker getWebContainerTracker() {
return webContainerTracker;
}
protected HttpContext getHttpContext() {
return httpContext;
}
public void setHttpContext(HttpContext httpContext) {
this.httpContext = httpContext;
}
public void start() {
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
@Override
public Object addingService(ServiceReference reference) {
return addingWebContainerCallback(reference);
}
@Override
public void modifiedService(ServiceReference reference, Object service) {
}
@Override
public void removedService(ServiceReference reference, Object service) {
removingWebContainerCallback(reference);
}
};
webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer);
webContainerTracker.open();
}
public void stop() {
webContainerTracker.remove(webContainerTracker.getServiceReference());
}
protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) {
WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference);
if (httpContext == null) {
httpContext = service.createDefaultHttpContext();
}
if (constraintMappings == null) {
throw new IllegalStateException("constraintMappings was null!");
}
for (PaxWebSecurityConstraintMapping constraintMapping : constraintMappings) {
addConstraintMapping(service, constraintMapping);
}
service.registerLoginConfig("KEYCLOAK", "specified-in-keycloak-json", null, null, httpContext);
return service;
}
protected void addConstraintMapping(WebContainer service, PaxWebSecurityConstraintMapping cm) {
log.debug("Adding security constraint name=" + cm.getConstraintName() + ", url=" + cm.getUrl() + ", dataConstraint=" + cm.getDataConstraint() + ", canAuthenticate="
+ cm.isAuthentication() + ", roles=" + cm.getRoles());
service.registerConstraintMapping(
cm.getConstraintName(),
cm.getMapping(),
cm.getUrl(),
cm.getDataConstraint(),
cm.isAuthentication(),
cm.getRoles(),
httpContext
);
}
protected void removingWebContainerCallback(ServiceReference serviceReference) {
WebContainer service = (WebContainer)bundleContext.getService(serviceReference);
if (service != null) {
service.unregisterLoginConfig(httpContext);
service.unregisterConstraintMapping(httpContext);
}
}
}
@@ -1 +0,0 @@
org.keycloak.adapters.osgi.undertow.KeycloakAuthenticatorService
+1 -10
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -63,15 +63,6 @@
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
</dependency>
</dependencies>
<build>
@@ -1,702 +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.installed;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jwe.*;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
import org.keycloak.util.JsonSerialization;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.*;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.*;
/**
* All kcinit commands that take input ask for
* <p>
* 1. . kcinit
* - setup and export KC_SESSION_KEY env var if not set.
* - checks to see if master token valid, refresh is possible, exit if token valid
* - performs command line login
* - stores master token for master client
* 2. app.sh is a wrapper for app cli.
* - token=`kcinit token app`
* - checks to see if token for app client has been fetched, refresh if valid, output token to sys.out if exists
* - if no token, login. Prompts go to stderr.
* - pass token as cmd line param to app or as environment variable.
* <p>
* 3. kcinit password {password}
* - outputs password key that is used for encryption.
* - can be used in .bashrc as export KC_SESSSION_KEY=`kcinit password {password}` or just set it in .bat file
* <p>
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KcinitDriver {
public static final String KC_SESSION_KEY = "KC_SESSION_KEY";
public static final String KC_LOGIN_CONFIG_PATH = "KC_LOGIN_CONFIG_PATH";
protected Map<String, String> config;
protected boolean debug = true;
protected static byte[] salt = new byte[]{-4, 88, 66, -101, 78, -94, 21, 105};
String[] args = null;
protected boolean forceLogin;
protected boolean browserLogin;
public void mainCmd(String[] args) throws Exception {
this.args = args;
if (args.length == 0) {
printHelp();
return;
}
if (args[0].equalsIgnoreCase("token")) {
//System.err.println("executing token");
token();
} else if (args[0].equalsIgnoreCase("login")) {
login();
} else if (args[0].equalsIgnoreCase("logout")) {
logout();
} else if (args[0].equalsIgnoreCase("env")) {
System.out.println(System.getenv().toString());
} else if (args[0].equalsIgnoreCase("install")) {
install();
} else if (args[0].equalsIgnoreCase("uninstall")) {
uninstall();
} else if (args[0].equalsIgnoreCase("password")) {
passwordKey();
} else {
KeycloakInstalled.console().writer().println("Unknown command: " + args[0]);
KeycloakInstalled.console().writer().println();
printHelp();
}
}
public String getHome() {
String home = System.getenv("HOME");
if (home == null) {
home = System.getProperty("HOME");
if (home == null) {
home = Paths.get("").toAbsolutePath().normalize().toString();
}
}
return home;
}
public void passwordKey() {
if (args.length < 2) {
printHelp();
System.exit(1);
}
String password = args[1];
try {
String encodedKey = generateEncryptionKey(password);
System.out.printf(encodedKey);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
protected String generateEncryptionKey(String password) throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100, 128);
SecretKey tmp = factory.generateSecret(spec);
byte[] aeskey = tmp.getEncoded();
return Base64.encodeBytes(aeskey);
}
public JWE createJWE() {
String key = getEncryptionKey();
if (key == null) {
throw new RuntimeException(KC_SESSION_KEY + " env var not set");
}
byte[] aesKey = null;
try {
aesKey = Base64.decode(key.getBytes("UTF-8"));
} catch (IOException e) {
throw new RuntimeException("invalid " + KC_SESSION_KEY + "env var");
}
JWE jwe = new JWE();
final SecretKey aesSecret = new SecretKeySpec(aesKey, "AES");
jwe.getKeyStorage()
.setEncryptionKey(aesSecret);
return jwe;
}
protected String encryptionKey;
protected String getEncryptionKey() {
if (encryptionKey != null) return encryptionKey;
return System.getenv(KC_SESSION_KEY);
}
public String encrypt(String payload) {
JWE jwe = createJWE();
JWEHeader jweHeader = new JWEHeader(JWEConstants.A128KW, JWEConstants.A128CBC_HS256, null);
try {
jwe.header(jweHeader).content(payload.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("cannot encode payload as UTF-8");
}
try {
return jwe.encodeJwe();
} catch (JWEException e) {
throw new RuntimeException("cannot encrypt payload", e);
}
}
public String decrypt(String encoded) {
JWE jwe = createJWE();
try {
jwe.verifyAndDecodeJwe(encoded);
byte[] content = jwe.getContent();
if (content == null) return null;
return new String(content, "UTF-8");
} catch (Exception ex) {
throw new RuntimeException("cannot decrypt payload", ex);
}
}
public static String getenv(String name, String defaultValue) {
String val = System.getenv(name);
return val == null ? defaultValue : val;
}
public File getConfigDirectory() {
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit").toFile();
}
public File getConfigFile() {
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "config.json").toFile();
}
public File getTokenFilePath(String client) {
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens", client).toFile();
}
public File getTokenDirectory() {
return Paths.get(getHome(), getenv(KC_LOGIN_CONFIG_PATH, ".keycloak"), "kcinit", "tokens").toFile();
}
protected boolean encrypted = false;
protected void checkEnv() {
File configFile = getConfigFile();
if (!configFile.exists()) {
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
System.exit(1);
}
byte[] data = new byte[0];
try {
data = readFileRaw(configFile);
} catch (IOException e) {
}
if (data == null) {
KeycloakInstalled.console().writer().println("Config file unreadable. Please run 'kcinit install' to configure.");
System.exit(1);
}
String encodedJwe = null;
try {
encodedJwe = new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (encodedJwe.contains("realm")) {
encrypted = false;
return;
} else {
encrypted = true;
}
if (System.getenv(KC_SESSION_KEY) == null) {
promptLocalPassword();
}
}
protected void promptLocalPassword() {
String password = KeycloakInstalled.console().passwordPrompt("Enter password to unlock kcinit config files: ");
try {
encryptionKey = generateEncryptionKey(password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected String readFile(File fp) {
try {
byte[] data = readFileRaw(fp);
if (data == null) return null;
String file = new String(data, "UTF-8");
if (!encrypted) {
return file;
}
String decrypted = decrypt(file);
if (decrypted == null)
throw new RuntimeException("Unable to decrypt file. Did you set your local password correctly?");
return decrypted;
} catch (IOException e) {
throw new RuntimeException("failed to decrypt file: " + fp.getAbsolutePath() + " Did you set your local password correctly?", e);
}
}
protected byte[] readFileRaw(File fp) throws IOException {
if (!fp.exists()) return null;
FileInputStream fis = new FileInputStream(fp);
byte[] data = new byte[(int) fp.length()];
fis.read(data);
fis.close();
return data;
}
protected void writeFile(File fp, String payload) {
try {
String data = payload;
if (encrypted) data = encrypt(payload);
FileOutputStream fos = new FileOutputStream(fp);
fos.write(data.getBytes("UTF-8"));
fos.flush();
fos.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void install() {
if (getEncryptionKey() == null) {
if (KeycloakInstalled.console().confirm("Do you want to protect tokens stored locally with a password? (y/n): ")) {
String password = "p";
String confirm = "c";
do {
password = KeycloakInstalled.console().passwordPrompt("Enter local password: ");
confirm = KeycloakInstalled.console().passwordPrompt("Confirm local password: ");
if (!password.equals(confirm)) {
KeycloakInstalled.console().writer().println();
KeycloakInstalled.console().writer().println("Confirmation does not match. Try again.");
KeycloakInstalled.console().writer().println();
}
} while (!password.equals(confirm));
try {
this.encrypted = true;
this.encryptionKey = generateEncryptionKey(password);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
} else {
if (!KeycloakInstalled.console().confirm("KC_SESSION_KEY env var already set. Do you want to use this as your local encryption key? (y/n): ")) {
KeycloakInstalled.console().writer().println("Unset KC_SESSION_KEY env var and run again");
System.exit(1);
}
this.encrypted = true;
this.encryptionKey = getEncryptionKey();
}
String server = KeycloakInstalled.console().readLine("Authentication server URL [http://localhost:8080/auth]: ").trim();
String realm = KeycloakInstalled.console().readLine("Name of realm [master]: ").trim();
String client = KeycloakInstalled.console().readLine("CLI client id [kcinit]: ").trim();
String secret = KeycloakInstalled.console().readLine("CLI client secret [none]: ").trim();
if (server.equals("")) {
server = "http://localhost:8080/auth";
}
if (realm.equals("")) {
realm = "master";
}
if (client.equals("")) {
client = "kcinit";
}
File configDir = getTokenDirectory();
configDir.mkdirs();
File configFile = getConfigFile();
Map<String, String> props = new HashMap<>();
props.put("server", server);
props.put("realm", realm);
props.put("client", client);
props.put("secret", secret);
try {
String json = JsonSerialization.writeValueAsString(props);
writeFile(configFile, json);
} catch (Exception e) {
e.printStackTrace();
}
KeycloakInstalled.console().writer().println();
KeycloakInstalled.console().writer().println("Installation complete!");
KeycloakInstalled.console().writer().println();
}
public void printHelp() {
KeycloakInstalled.console().writer().println("Commands:");
KeycloakInstalled.console().writer().println(" login [-f] -f forces login");
KeycloakInstalled.console().writer().println(" logout");
KeycloakInstalled.console().writer().println(" token [client] - print access token of desired client. Defaults to default master client. Will print either 'error', 'not-allowed', or 'login-required' on error.");
KeycloakInstalled.console().writer().println(" install - Install this utility. Will store in $HOME/.keycloak/kcinit unless " + KC_LOGIN_CONFIG_PATH + " env var is set");
System.exit(1);
}
public AdapterConfig getConfig() {
File configFile = getConfigFile();
if (!configFile.exists()) {
KeycloakInstalled.console().writer().println("You have not configured kcinit. Please run 'kcinit install' to configure.");
System.exit(1);
return null;
}
AdapterConfig config = new AdapterConfig();
config.setAuthServerUrl((String) getConfigProperties().get("server"));
config.setRealm((String) getConfigProperties().get("realm"));
config.setResource((String) getConfigProperties().get("client"));
config.setSslRequired("external");
String secret = (String) getConfigProperties().get("secret");
if (secret != null && !secret.trim().equals("")) {
Map<String, Object> creds = new HashMap<>();
creds.put("secret", secret);
config.setCredentials(creds);
} else {
config.setPublicClient(true);
}
return config;
}
private Map<String, String> getConfigProperties() {
if (this.config != null) return this.config;
if (!getConfigFile().exists()) {
KeycloakInstalled.console().writer().println();
KeycloakInstalled.console().writer().println(("Config file does not exist. Run kcinit install to set it up."));
System.exit(1);
}
String json = readFile(getConfigFile());
try {
Map map = JsonSerialization.readValue(json, Map.class);
config = (Map<String, String>) map;
} catch (IOException e) {
throw new RuntimeException(e);
}
return this.config;
}
public String readToken(String client) throws Exception {
String json = getTokenResponse(client);
if (json == null) return null;
if (json != null) {
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
return tokenResponse.getToken();
}
AdapterConfig config = getConfig();
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
installed.refreshToken(tokenResponse.getRefreshToken());
processResponse(installed, client);
return tokenResponse.getToken();
} catch (Exception e) {
File tokenFile = getTokenFilePath(client);
if (tokenFile.exists()) {
tokenFile.delete();
}
return null;
}
}
return null;
}
public String readRefreshToken(String client) throws Exception {
String json = getTokenResponse(client);
if (json == null) return null;
if (json != null) {
try {
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
return tokenResponse.getRefreshToken();
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
File tokenFile = getTokenFilePath(client);
if (tokenFile.exists()) {
tokenFile.delete();
}
return null;
}
}
return null;
}
private String getTokenResponse(String client) throws IOException {
File tokenFile = getTokenFilePath(client);
try {
return readFile(tokenFile);
} catch (Exception e) {
if (debug) {
System.err.println("Failed to read encrypted file");
e.printStackTrace();
}
if (tokenFile.exists()) tokenFile.delete();
return null;
}
}
public void token() throws Exception {
KeycloakInstalled.console().stderrOutput();
checkEnv();
String masterClient = getMasterClient();
String client = masterClient;
if (args.length > 1) {
client = args[1];
}
//System.err.println("readToken: " + client);
String token = readToken(client);
if (token != null) {
System.out.print(token);
return;
}
if (token == null && client.equals(masterClient)) {
//System.err.println("not logged in, logging in.");
doConsoleLogin();
token = readToken(client);
if (token != null) {
System.out.print(token);
return;
}
}
String masterToken = readToken(masterClient);
if (masterToken == null) {
//System.err.println("not logged in, logging in.");
doConsoleLogin();
masterToken = readToken(masterClient);
if (masterToken == null) {
System.err.println("Login failed. Cannot retrieve token");
System.exit(1);
}
}
//System.err.println("exchange: " + client);
Client httpClient = getHttpClient();
WebTarget exchangeUrl = httpClient.target(getServer())
.path("/realms")
.path(getRealm())
.path("protocol/openid-connect/token");
Form form = new Form()
.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE)
.param(OAuth2Constants.CLIENT_ID, masterClient)
.param(OAuth2Constants.SUBJECT_TOKEN, masterToken)
.param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE)
.param(OAuth2Constants.REQUESTED_TOKEN_TYPE, OAuth2Constants.REFRESH_TOKEN_TYPE)
.param(OAuth2Constants.AUDIENCE, client);
if (getMasterClientSecret() != null) {
form.param(OAuth2Constants.CLIENT_SECRET, getMasterClientSecret());
}
Response response = exchangeUrl.request().post(Entity.form(
form
));
if (response.getStatus() == 401 || response.getStatus() == 403) {
response.close();
System.err.println("Not allowed to exchange for client token");
System.exit(1);
}
if (response.getStatus() != 200) {
if (response.getMediaType() != null && response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE)) {
try {
String json = response.readEntity(String.class);
OAuth2ErrorRepresentation error = JsonSerialization.readValue(json, OAuth2ErrorRepresentation.class);
System.err.println("Failed to exchange token: " + error.getError() + ". " + error.getErrorDescription());
System.exit(1);
} catch (Exception ignore) {
ignore.printStackTrace();
}
}
response.close();
System.err.println("Unknown error exchanging for client token: " + response.getStatus());
System.exit(1);
}
String json = response.readEntity(String.class);
response.close();
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
if (tokenResponse.getToken() != null) {
getTokenDirectory().mkdirs();
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
tokenResponse.setIdToken(null);
json = JsonSerialization.writeValueAsString(tokenResponse);
writeFile(getTokenFilePath(client), json);
System.out.printf(tokenResponse.getToken());
} else {
System.err.println("Error processing token");
System.exit(1);
}
}
protected String getMasterClientSecret() {
return getProperty("secret");
}
protected String getServer() {
return getProperty("server");
}
protected String getRealm() {
return getProperty("realm");
}
public String getProperty(String name) {
return (String) getConfigProperties().get(name);
}
protected boolean forceLogin() {
return args.length > 0 && args[0].equals("-f");
}
public Client getHttpClient() {
return new ResteasyClientBuilder().disableTrustManager().build();
}
public void login() throws Exception {
checkEnv();
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
for (String arg : args) {
if (arg.equals("-f") || arg.equals("-force")) {
forceLogin = true;
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
} else if (arg.equals("-browser") || arg.equals("-b")) {
browserLogin = true;
this.args = Arrays.copyOfRange(this.args, 1, this.args.length);
} else {
System.err.println("Illegal argument: " + arg);
printHelp();
System.exit(1);
}
}
String masterClient = getMasterClient();
if (!forceLogin && readToken(masterClient) != null) {
KeycloakInstalled.console().writer().println("Already logged in. `kcinit -f` to force relogin");
return;
}
doConsoleLogin();
KeycloakInstalled.console().writer().println("Login successful!");
}
public void doConsoleLogin() throws Exception {
String masterClient = getMasterClient();
AdapterConfig config = getConfig();
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
//System.err.println("calling loginCommandLine");
if (!installed.loginCommandLine()) {
System.exit(1);
}
processResponse(installed, masterClient);
}
private String getMasterClient() {
return getProperty("client");
}
private void processResponse(KeycloakInstalled installed, String client) throws IOException {
AccessTokenResponse tokenResponse = installed.getTokenResponse();
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
tokenResponse.setIdToken(null);
String json = JsonSerialization.writeValueAsString(tokenResponse);
getTokenDirectory().mkdirs();
writeFile(getTokenFilePath(client), json);
}
public void logout() throws Exception {
String token = readRefreshToken(getMasterClient());
if (token != null) {
try {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getConfig());
ServerRequest.invokeLogout(deployment, token);
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
}
}
if (getTokenDirectory().exists()) {
for (File fp : getTokenDirectory().listFiles()) fp.delete();
}
}
public void uninstall() throws Exception {
File configFile = getConfigFile();
if (configFile.exists()) configFile.delete();
if (getTokenDirectory().exists()) {
for (File fp : getTokenDirectory().listFiles()) fp.delete();
}
}
}
@@ -17,8 +17,6 @@
package org.keycloak.adapters.installed;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.adapters.KeycloakDeployment;
@@ -26,41 +24,33 @@ import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.ServerRequest;
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.awt.*;
import java.io.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeycloakInstalled {
public interface HttpResponseWriter {
void success(PrintWriter pw, KeycloakInstalled ki);
void failure(PrintWriter pw, KeycloakInstalled ki);
}
private static final String KEYCLOAK_JSON = "META-INF/keycloak.json";
private KeycloakDeployment deployment;
@@ -69,21 +59,12 @@ public class KeycloakInstalled {
LOGGED_MANUAL, LOGGED_DESKTOP
}
private AccessTokenResponse tokenResponse;
private String tokenString;
private String idTokenString;
private IDToken idToken;
private AccessToken token;
private String refreshToken;
private Status status;
private Locale locale;
private HttpResponseWriter loginResponseWriter;
private HttpResponseWriter logoutResponseWriter;
private ResteasyClient resteasyClient;
Pattern callbackPattern = Pattern.compile("callback\\s*=\\s*\"([^\"]+)\"");
Pattern paramPattern = Pattern.compile("param=\"([^\"]+)\"\\s+label=\"([^\"]+)\"\\s+mask=(\\S+)");
Pattern codePattern = Pattern.compile("code=([^&]+)");
public KeycloakInstalled() {
InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
@@ -94,38 +75,6 @@ public class KeycloakInstalled {
deployment = KeycloakDeploymentBuilder.build(config);
}
public KeycloakInstalled(KeycloakDeployment deployment) {
this.deployment = deployment;
}
public HttpResponseWriter getLoginResponseWriter() {
return null;
}
public HttpResponseWriter getLogoutResponseWriter() {
return null;
}
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
this.loginResponseWriter = loginResponseWriter;
}
public void setLogoutResponseWriter(HttpResponseWriter logoutResponseWriter) {
this.logoutResponseWriter = logoutResponseWriter;
}
public void setResteasyClient(ResteasyClient resteasyClient) {
this.resteasyClient = resteasyClient;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
if (isDesktopSupported()) {
loginDesktop();
@@ -159,22 +108,19 @@ public class KeycloakInstalled {
}
public void loginDesktop() throws IOException, VerificationException, OAuthErrorException, URISyntaxException, ServerRequest.HttpFailure, InterruptedException {
CallbackListener callback = new CallbackListener(getLoginResponseWriter());
CallbackListener callback = new CallbackListener();
callback.start();
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
String state = UUID.randomUUID().toString();
KeycloakUriBuilder builder = deployment.getAuthUrl().clone()
String authUrl = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam(OAuth2Constants.STATE, state)
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID);
if (locale != null) {
builder.queryParam(OAuth2Constants.UI_LOCALES_PARAM, locale.getLanguage());
}
String authUrl = builder.build().toString();
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
.build().toString();
Desktop.getDesktop().browse(new URI(authUrl));
@@ -198,7 +144,7 @@ public class KeycloakInstalled {
}
private void logoutDesktop() throws IOException, URISyntaxException, InterruptedException {
CallbackListener callback = new CallbackListener(getLogoutResponseWriter());
CallbackListener callback = new CallbackListener();
callback.start();
String redirectUri = "http://localhost:" + callback.server.getLocalPort();
@@ -221,6 +167,9 @@ public class KeycloakInstalled {
}
public void loginManual(PrintStream printer, Reader reader) throws IOException, ServerRequest.HttpFailure, VerificationException {
CallbackListener callback = new CallbackListener();
callback.start();
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
String authUrl = deployment.getAuthUrl().clone()
@@ -241,272 +190,6 @@ public class KeycloakInstalled {
status = Status.LOGGED_MANUAL;
}
public static class Console {
protected java.io.Console console = System.console();
protected PrintWriter writer;
protected BufferedReader reader;
static Console SINGLETON = new Console();
private Console() {
}
public PrintWriter writer() {
if (console == null) {
if (writer == null) {
writer = new PrintWriter(System.err, true);
}
return writer;
}
return console.writer();
}
public Reader reader() {
if (console == null) {
return getReader();
}
return console.reader();
}
protected BufferedReader getReader() {
if (reader != null) return reader;
reader = new BufferedReader(new BufferedReader(new InputStreamReader(System.in)));
return reader;
}
public Console format(String fmt, Object... args) {
if (console == null) {
writer().format(fmt, args);
return this;
}
console.format(fmt, args);
return this;
}
public Console printf(String format, Object... args) {
if (console == null) {
writer().printf(format, args);
return this;
}
console.printf(format, args);
return this;
}
public String readLine(String fmt, Object... args) {
if (console == null) {
format(fmt, args);
return readLine();
}
return console.readLine(fmt, args);
}
public boolean confirm(String fmt, Object... args) {
String prompt = "";
while (!"y".equals(prompt) && !"n".equals(prompt)) {
prompt = readLine(fmt, args);
}
return "y".equals(prompt);
}
public String prompt(String fmt, Object... args) {
String prompt = "";
while (prompt.equals("")) {
prompt = readLine(fmt, args).trim();
}
return prompt;
}
public String passwordPrompt(String fmt, Object... args) {
String prompt = "";
while (prompt.equals("")) {
char[] val = readPassword(fmt, args);
prompt = new String(val);
prompt = prompt.trim();
}
return prompt;
}
public String readLine() {
if (console == null) {
try {
return getReader().readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return console.readLine();
}
public char[] readPassword(String fmt, Object... args) {
if (console == null) {
return readLine(fmt, args).toCharArray();
}
return console.readPassword(fmt, args);
}
public char[] readPassword() {
if (console == null) {
return readLine().toCharArray();
}
return console.readPassword();
}
public void flush() {
if (console == null) {
System.err.flush();
return;
}
console.flush();
}
public void stderrOutput() {
//System.err.println("not using System.console()");
console = null;
}
}
public static Console console() {
return Console.SINGLETON;
}
public boolean loginCommandLine() throws IOException, ServerRequest.HttpFailure, VerificationException {
String redirectUri = "urn:ietf:wg:oauth:2.0:oob";
return loginCommandLine(redirectUri);
}
/**
* Experimental proprietary WWW-Authentication challenge protocol.
* WWW-Authentication: X-Text-Form-Challenge callback="{url}" param="{param-name}" label="{param-display-label}"
*
* @param redirectUri
* @return
* @throws IOException
* @throws ServerRequest.HttpFailure
* @throws VerificationException
*/
public boolean loginCommandLine(String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
String authUrl = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri)
.queryParam("display", "console")
.queryParam(OAuth2Constants.SCOPE, OAuth2Constants.SCOPE_OPENID)
.build().toString();
ResteasyClient client = createResteasyClient();
try {
//System.err.println("initial request");
Response response = client.target(authUrl).request().get();
while (true) {
if (response.getStatus() == 403) {
if (response.getMediaType() != null) {
String splash = response.readEntity(String.class);
console().writer().println(splash);
} else {
System.err.println("Forbidden to login");
}
return false;
} else if (response.getStatus() == 401) {
String authenticationHeader = response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE);
if (authenticationHeader == null) {
System.err.println("Failure: Invalid protocol. No WWW-Authenticate header");
return false;
}
//System.err.println("got header: " + authenticationHeader);
if (!authenticationHeader.contains("X-Text-Form-Challenge")) {
System.err.println("Failure: Invalid WWW-Authenticate header.");
return false;
}
if (response.getMediaType() != null) {
String splash = response.readEntity(String.class);
console().writer().println(splash);
} else {
response.close();
}
Matcher m = callbackPattern.matcher(authenticationHeader);
if (!m.find()) {
System.err.println("Failure: Invalid WWW-Authenticate header.");
return false;
}
String callback = m.group(1);
//System.err.println("callback: " + callback);
m = paramPattern.matcher(authenticationHeader);
Form form = new Form();
while (m.find()) {
String param = m.group(1);
String label = m.group(2);
String mask = m.group(3).trim();
boolean maskInput = mask.equals("true");
String value = null;
if (maskInput) {
char[] txt = console().readPassword(label);
value = new String(txt);
} else {
value = console().readLine(label);
}
form.param(param, value);
}
response.close();
client.close();
client = createResteasyClient();
response = client.target(callback).request().post(Entity.form(form));
} else if (response.getStatus() == 302) {
int redirectCount = 0;
do {
String location = response.getLocation().toString();
Matcher m = codePattern.matcher(location);
if (!m.find()) {
response.close();
client.close();
client = createResteasyClient();
response = client.target(location).request().get();
} else {
response.close();
client.close();
String code = m.group(1);
processCode(code, redirectUri);
return true;
}
if (response.getStatus() == 302 && redirectCount++ > 4) {
System.err.println("Too many redirects. Aborting");
return false;
}
} while (response.getStatus() == 302);
} else {
System.err.println("Unknown response from server: " + response.getStatus());
return false;
}
}
} catch (Exception ex) {
throw ex;
} finally {
client.close();
}
}
protected ResteasyClient getResteasyClient() {
if (this.resteasyClient == null) {
this.resteasyClient = createResteasyClient();
}
return this.resteasyClient;
}
protected ResteasyClient createResteasyClient() {
return new ResteasyClientBuilder()
.connectionCheckoutTimeout(1, TimeUnit.HOURS)
.connectionTTL(1, TimeUnit.HOURS)
.socketTimeout(1, TimeUnit.HOURS)
.disableTrustManager().build();
}
public String getTokenString() throws VerificationException, IOException, ServerRequest.HttpFailure {
return tokenString;
}
@@ -525,14 +208,7 @@ public class KeycloakInstalled {
parseAccessToken(tokenResponse);
}
public void refreshToken(String refreshToken) throws IOException, ServerRequest.HttpFailure, VerificationException {
AccessTokenResponse tokenResponse = ServerRequest.invokeRefresh(deployment, refreshToken);
parseAccessToken(tokenResponse);
}
private void parseAccessToken(AccessTokenResponse tokenResponse) throws VerificationException {
this.tokenResponse = tokenResponse;
tokenString = tokenResponse.getToken();
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
@@ -564,10 +240,6 @@ public class KeycloakInstalled {
return refreshToken;
}
public AccessTokenResponse getTokenResponse() {
return tokenResponse;
}
public boolean isDesktopSupported() {
return Desktop.isDesktopSupported();
}
@@ -576,7 +248,6 @@ public class KeycloakInstalled {
return deployment;
}
private void processCode(String code, String redirectUri) throws IOException, ServerRequest.HttpFailure, VerificationException {
AccessTokenResponse tokenResponse = ServerRequest.invokeAccessCodeToToken(deployment, code, redirectUri, null);
parseAccessToken(tokenResponse);
@@ -598,7 +269,6 @@ public class KeycloakInstalled {
return sb.toString();
}
public class CallbackListener extends Thread {
private ServerSocket server;
@@ -613,19 +283,14 @@ public class KeycloakInstalled {
private String state;
private Socket socket;
private HttpResponseWriter writer;
public CallbackListener(HttpResponseWriter writer) throws IOException {
this.writer = writer;
public CallbackListener() throws IOException {
server = new ServerSocket(0);
}
@Override
public void run() {
try {
socket = server.accept();
Socket socket = server.accept();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String request = br.readLine();
@@ -649,30 +314,10 @@ public class KeycloakInstalled {
}
}
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
PrintWriter pw = new PrintWriter(out);
if (writer != null) {
System.err.println("Using a writer is deprecated. Please remove its usage. This is now handled by endpoint on server");
}
if (error == null) {
if (writer != null) {
writer.success(pw, KeycloakInstalled.this);
} else {
pw.println("HTTP/1.1 302 Found");
pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated"));
}
} else {
if (writer != null) {
writer.failure(pw, KeycloakInstalled.this);
} else {
pw.println("HTTP/1.1 302 Found");
pw.println("Location: " + deployment.getTokenUrl().replace("/token", "/delegated?error=true"));
}
}
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
pw.println("Please close window and return to application");
pw.flush();
socket.close();
} catch (IOException e) {
errorException = e;
@@ -683,8 +328,6 @@ public class KeycloakInstalled {
} catch (IOException e) {
}
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -27,9 +27,6 @@ import javax.security.cert.X509Certificate;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.SecurityContext;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
@@ -54,8 +51,6 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
protected class RequestFacade implements OIDCHttpFacade.Request {
private InputStream inputStream;
@Override
public String getFirstParam(String param) {
throw new RuntimeException("NOT IMPLEMENTED");
@@ -113,19 +108,6 @@ public class JaxrsHttpFacade implements OIDCHttpFacade {
@Override
public InputStream getInputStream() {
return getInputStream(false);
}
@Override
public InputStream getInputStream(boolean buffered) {
if (inputStream != null) {
return inputStream;
}
if (buffered) {
return inputStream = new BufferedInputStream(requestContext.getEntityStream());
}
return requestContext.getEntityStream();
}
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -106,8 +106,6 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
public abstract JettyUserSessionManagement createSessionManagement(Request request);
public void logoutCurrent(Request request) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
@@ -289,7 +287,7 @@ public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthentica
log.debug("*** deployment isn't configured return false");
return Authentication.UNAUTHENTICATED;
}
PreAuthActionsHandler handler = new PreAuthActionsHandler(createSessionManagement(request), deploymentContext, facade);
PreAuthActionsHandler handler = new PreAuthActionsHandler(new JettyUserSessionManagement(request.getSessionManager()), deploymentContext, facade);
if (handler.handleRequest()) {
return Authentication.SEND_SUCCESS;
}
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<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);
}
}
@@ -25,7 +25,6 @@ import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import javax.servlet.ServletRequest;
@@ -45,11 +44,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty81SessionManager(request.getSessionManager()));
}
@Override
protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<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);
}
}
@@ -27,7 +27,6 @@ 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;
@@ -47,11 +46,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty91SessionManager(request.getSessionManager()));
}
@Override
protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<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);
}
}
@@ -27,7 +27,6 @@ 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;
@@ -62,11 +61,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
public JettyUserSessionManagement createSessionManagement(Request request) {
return new JettyUserSessionManagement(new Jetty92SessionManager(request.getSessionManager()));
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
+1 -1
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<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 Jetty93SessionManager implements JettySessionManager {
protected SessionManager sessionManager;
public Jetty93SessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
@Override
public HttpSession getHttpSession(String id) {
return sessionManager.getHttpSession(id);
}
}
@@ -26,7 +26,6 @@ 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;
@@ -61,11 +60,6 @@ public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticat
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) {
-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>4.1.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);
}
}
+1 -15
View File
@@ -20,7 +20,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Jetty Integration</name>
@@ -37,18 +37,4 @@
<module>jetty9.2</module>
<module>jetty9.3</module>
</modules>
<profiles>
<profile>
<id>community</id>
<activation>
<property>
<name>!product</name>
</property>
</activation>
<modules>
<module>jetty9.4</module>
</modules>
</profile>
</profiles>
</project>
+1 -6
View File
@@ -21,7 +21,7 @@
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>4.1.0.Final</version>
<version>2.5.5.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -38,11 +38,6 @@
<plugin>
<groupId>com.samaxes.maven</groupId>
<artifactId>minify-maven-plugin</artifactId>
<configuration>
<jsEngine>CLOSURE</jsEngine>
<closureLanguageIn>ECMASCRIPT5</closureLanguageIn>
<closureCreateSourceMap>true</closureCreateSourceMap>
</configuration>
<executions>
<execution>
<id>min-js</id>
-122
View File
@@ -1,122 +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 AuthorizationRequest {
/**
* An array of objects representing the resource and scopes.
*/
permissions?:ResourcePermission[],
/**
* A permission ticket obtained from a resource server when using UMA authorization protocol.
*/
ticket?:string,
/**
* A boolean value indicating whether the server should create permission requests to the resources
* and scopes referenced by a permission ticket. This parameter will only take effect when used together
* with the ticket parameter as part of a UMA authorization process.
*/
submitRequest?:boolean,
/**
* Defines additional information about this authorization request in order to specify how it should be processed
* by the server.
*/
metadata?:AuthorizationRequestMetadata,
/**
* Defines whether or not this authorization request should include the current RPT. If set to true, the RPT will
* be sent and permissions in the current RPT will be included in the new RPT. Otherwise, only the permissions referenced in this
* authorization request will be granted in the new RPT.
*/
incrementalAuthorization?:boolean
}
interface AuthorizationRequestMetadata {
/**
* A boolean value indicating to the server if resource names should be included in the RPT’s permissions.
* If false, only the resource identifier is included.
*/
responseIncludeResourceName?:any,
/**
* An integer N that defines a limit for the amount of permissions an RPT can have. When used together with
* rpt parameter, only the last N requested permissions will be kept in the RPT.
*/
response_permissions_limit?:number
}
interface ResourcePermission {
/**
* The id or name of a resource.
*/
id:string,
/**
* An array of strings where each value is the name of a scope associated with the resource.
*/
scopes?:string[]
}
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 using UMA protocol.
*
* The authorization request must be provided with a ticket.
*
* @param authorizationRequest An AuthorizationRequest instance with a valid permission ticket set.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
authorize(authorizationRequest: AuthorizationRequest): KeycloakAuthorizationPromise;
/**
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
*
* @param resourceServerId The id (client id) of the resource server to obtain permissions from.
* @param authorizationRequest An AuthorizationRequest instance.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
entitlement(resourceServerId: string, authorizationRequest?: AuthorizationRequest): KeycloakAuthorizationPromise;
}
}
@@ -18,14 +18,14 @@
(function( window, undefined ) {
var KeycloakAuthorization = function (keycloak, options) {
var KeycloakAuthorization = function (keycloak) {
var _instance = this;
this.rpt = null;
this.init = function () {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma-configuration');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
@@ -41,65 +41,74 @@
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer using UMA protocol.
* policy enforcer.
*
* The authorization request must be provided with a ticket.
* 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.
*/
this.authorize = function (authorizationRequest) {
this.authorize = function (wwwAuthenticateHeader) {
this.then = function (onGrant, onDeny, onError) {
if (authorizationRequest && authorizationRequest.ticket) {
var request = new XMLHttpRequest();
if (wwwAuthenticateHeader.indexOf('UMA') != -1) {
var params = wwwAuthenticateHeader.split(',');
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
for (i = 0; i < params.length; i++) {
var param = params[i].split('=');
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (param[0] == 'ticket') {
var request = new XMLHttpRequest();
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
request.open('POST', _instance.config.rpt_endpoint, true);
request.setRequestHeader('Content-Type', 'application/json')
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).rpt;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
};
var ticket = param[1].substring(1, param[1].length - 1).trim();
request.send(JSON.stringify(
{
ticket: ticket,
rpt: _instance.rpt
}
}
));
}
};
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
if (authorizationRequest.submitRequest != undefined) {
params += "&submit_request=" + authorizationRequest.submitRequest;
}
} else if (wwwAuthenticateHeader.indexOf('KC_ETT') != -1) {
var params = wwwAuthenticateHeader.substring('KC_ETT'.length).trim().split(',');
var clientId = null;
var metadata = authorizationRequest.metadata;
for (i = 0; i < params.length; i++) {
var param = params[i].split('=');
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
if (param[0] == 'realm') {
clientId = param[1].substring(1, param[1].length - 1).trim();
}
}
if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
_instance.entitlement(clientId).then(onGrant, onDeny, onError);
}
};
@@ -107,22 +116,20 @@
};
/**
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
* Obtains all entitlements from a Keycloak Server based on a give resourceServerId.
*/
this.entitlement = function (resourceServerId, authorizationRequest) {
this.entitlement = function (resourceSeververId, entitlementRequest ) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
var rpt = JSON.parse(request.responseText).rpt;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
@@ -141,70 +148,25 @@
}
};
if (!authorizationRequest) {
authorizationRequest = {};
var erJson = null
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);
}
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token)
request.send(erJson);
if (authorizationRequest.claimToken) {
params += "&claim_token=" + authorizationRequest.claimToken;
if (authorizationRequest.claimTokenFormat) {
params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
}
}
params += "&audience=" + resourceServerId;
var permissions = authorizationRequest.permissions;
if (!permissions) {
permissions = [];
}
for (i = 0; i < permissions.length; i++) {
var resource = permissions[i];
var permission = resource.id;
if (resource.scopes && resource.scopes.length > 0) {
permission += "#";
for (j = 0; j < resource.scopes.length; j++) {
var scope = resource.scopes[j];
if (permission.indexOf('#') != permission.length - 1) {
permission += ",";
}
permission += scope;
}
}
params += "&permission=" + permission;
}
var metadata = authorizationRequest.metadata;
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
}
}
if (_instance.rpt) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
};
return this;
};
this.init(this);
return this;
};
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
-503
View File
@@ -1,503 +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' | any;
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.
*/
useNonce?: boolean;
/**
* Allows to use different adapter:
*
* - {string} default - using browser api for redirects
* - {string} cordova - using cordova plugins
* - {function} - allows to provide custom function as adapter.
*/
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?: number;
/**
* 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;
/**
* Sets the 'ui_locales' query param in compliance with section 3.1.2.1
* of the OIDC 1.0 specification.
*/
locale?: string;
/**
* Specifies the desired Keycloak locale for the UI. This differs from
* the locale param in that it tells the Keycloak server to set a cookie and update
* the user's profile to a new preferred locale.
*/
kcLocale?: string;
/**
* Specifies arguments that are passed to the Cordova in-app-browser (if applicable).
* Options 'hidden' and 'location' are not affected by these arguments.
* All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/.
* Example of use: { zoom: "no", hardwareback: "yes" }
*/
cordovaOptions?: { [optionName: string]: 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>;
}
}
+174 -391
View File
@@ -33,15 +33,6 @@
interval: 5
};
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if ((scripts[i].src.indexOf('keycloak.js') !== -1 || scripts[i].src.indexOf('keycloak.min.js') !== -1) && scripts[i].src.indexOf('version=') !== -1) {
kc.iframeVersion = scripts[i].src.substring(scripts[i].src.indexOf('version=') + 8).split('&')[0];
}
}
var useNonce = true;
kc.init = function (initOptions) {
kc.authenticated = false;
@@ -51,10 +42,8 @@
adapter = loadAdapter('cordova');
} else if (initOptions && initOptions.adapter === 'default') {
adapter = loadAdapter();
} else if (initOptions && typeof initOptions.adapter === "object") {
adapter = initOptions.adapter;
} else {
if (window.Cordova || window.cordova) {
if (window.Cordova) {
adapter = loadAdapter('cordova');
} else {
adapter = loadAdapter();
@@ -62,10 +51,6 @@
}
if (initOptions) {
if (typeof initOptions.useNonce !== 'undefined') {
useNonce = initOptions.useNonce;
}
if (typeof initOptions.checkLoginIframe !== 'undefined') {
loginIframe.enable = initOptions.checkLoginIframe;
}
@@ -167,17 +152,12 @@
var callback = parseCallback(window.location.href);
if (callback) {
setupCheckLoginIframe();
window.history.replaceState({}, null, callback.newUrl);
}
if (callback && callback.valid) {
return setupCheckLoginIframe().success(function() {
processCallback(callback, initPromise);
}).error(function (e) {
initPromise.setError();
});
processCallback(callback, initPromise);
return;
} else if (initOptions) {
if (initOptions.token && initOptions.refreshToken) {
if (initOptions.refreshToken) {
setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken);
if (loginIframe.enable) {
@@ -234,7 +214,7 @@
var callbackState = {
state: state,
nonce: nonce,
redirectUri: encodeURIComponent(redirectUri)
redirectUri: encodeURIComponent(redirectUri),
}
if (options && options.prompt) {
@@ -243,34 +223,22 @@
callbackStorage.add(callbackState);
var baseUrl;
var action = 'auth';
if (options && options.action == 'register') {
baseUrl = kc.endpoints.register();
} else {
baseUrl = kc.endpoints.authorize();
action = 'registrations';
}
var scope;
if (options && options.scope) {
if (options.scope.indexOf("openid") != -1) {
scope = options.scope;
} else {
scope = "openid " + options.scope;
}
} else {
scope = "openid";
}
var scope = (options && options.scope) ? "openid " + options.scope : "openid";
var url = baseUrl
var url = getRealmUrl()
+ '/protocol/openid-connect/' + action
+ '?client_id=' + encodeURIComponent(kc.clientId)
+ '&redirect_uri=' + encodeURIComponent(redirectUri)
+ '&state=' + encodeURIComponent(state)
+ '&nonce=' + encodeURIComponent(nonce)
+ '&response_mode=' + encodeURIComponent(kc.responseMode)
+ '&response_type=' + encodeURIComponent(kc.responseType)
+ '&scope=' + encodeURIComponent(scope);
if (useNonce) {
url = url + '&nonce=' + encodeURIComponent(nonce);
}
if (options && options.prompt) {
url += '&prompt=' + encodeURIComponent(options.prompt);
@@ -291,10 +259,6 @@
if (options && options.locale) {
url += '&ui_locales=' + encodeURIComponent(options.locale);
}
if (options && options.kcLocale) {
url += '&kc_locale=' + encodeURIComponent(options.kcLocale);
}
return url;
}
@@ -304,7 +268,8 @@
}
kc.createLogoutUrl = function(options) {
var url = kc.endpoints.logout()
var url = getRealmUrl()
+ '/protocol/openid-connect/logout'
+ '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options, false));
return url;
@@ -323,14 +288,11 @@
}
kc.createAccountUrl = function(options) {
var realm = getRealmUrl();
var url = undefined;
if (typeof realm !== 'undefined') {
url = realm
var url = getRealmUrl()
+ '/account'
+ '?referrer=' + encodeURIComponent(kc.clientId)
+ '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options));
}
return url;
}
@@ -378,7 +340,7 @@
}
kc.loadUserInfo = function() {
var url = kc.endpoints.userinfo();
var url = getRealmUrl() + '/protocol/openid-connect/userinfo';
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.setRequestHeader('Accept', 'application/json');
@@ -443,7 +405,7 @@
promise.setSuccess(false);
} else {
var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken;
var url = kc.endpoints.token();
var url = getRealmUrl() + '/protocol/openid-connect/token';
refreshQueue.push(promise);
@@ -479,10 +441,6 @@
} else {
console.warn('[KEYCLOAK] Failed to refresh token');
if (req.status == 400) {
kc.clearToken();
}
kc.onAuthRefreshError && kc.onAuthRefreshError();
for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) {
p.setError(true);
@@ -521,14 +479,10 @@
}
function getRealmUrl() {
if (typeof kc.authServerUrl !== 'undefined') {
if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') {
return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm);
} else {
return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm);
}
if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') {
return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm);
} else {
return undefined;
return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm);
}
}
@@ -562,7 +516,7 @@
if ((kc.flow != 'implicit') && code) {
var params = 'code=' + code + '&grant_type=authorization_code';
var url = kc.endpoints.token();
var url = getRealmUrl() + '/protocol/openid-connect/token';
var req = new XMLHttpRequest();
req.open('POST', url, true);
@@ -599,9 +553,9 @@
setToken(accessToken, refreshToken, idToken, timeLocal);
if (useNonce && ((kc.tokenParsed && kc.tokenParsed.nonce != oauth.storedNonce) ||
if ((kc.tokenParsed && kc.tokenParsed.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');
kc.clearToken();
@@ -626,65 +580,6 @@
configUrl = config;
}
function setupOidcEndoints(oidcConfiguration) {
if (! oidcConfiguration) {
kc.endpoints = {
authorize: function() {
return getRealmUrl() + '/protocol/openid-connect/auth';
},
token: function() {
return getRealmUrl() + '/protocol/openid-connect/token';
},
logout: function() {
return getRealmUrl() + '/protocol/openid-connect/logout';
},
checkSessionIframe: function() {
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
if (kc.iframeVersion) {
src = src + '?version=' + kc.iframeVersion;
}
return src;
},
register: function() {
return getRealmUrl() + '/protocol/openid-connect/registrations';
},
userinfo: function() {
return getRealmUrl() + '/protocol/openid-connect/userinfo';
}
};
} else {
kc.endpoints = {
authorize: function() {
return oidcConfiguration.authorization_endpoint;
},
token: function() {
return oidcConfiguration.token_endpoint;
},
logout: function() {
if (!oidcConfiguration.end_session_endpoint) {
throw "Not supported by the OIDC server";
}
return oidcConfiguration.end_session_endpoint;
},
checkSessionIframe: function() {
if (!oidcConfiguration.check_session_iframe) {
throw "Not supported by the OIDC server";
}
return oidcConfiguration.check_session_iframe;
},
register: function() {
throw 'Redirection to "Register user" page not supported in standard OIDC mode';
},
userinfo: function() {
if (!oidcConfiguration.userinfo_endpoint) {
throw "Not supported by the OIDC server";
}
return oidcConfiguration.userinfo_endpoint;
}
}
}
}
if (configUrl) {
var req = new XMLHttpRequest();
req.open('GET', configUrl, true);
@@ -692,14 +587,14 @@
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) {
if (req.status == 200) {
var config = JSON.parse(req.responseText);
kc.authServerUrl = config['auth-server-url'];
kc.realm = config['realm'];
kc.clientId = config['resource'];
kc.clientSecret = (config['credentials'] || {})['secret'];
setupOidcEndoints(null);
promise.setSuccess();
} else {
promise.setError();
@@ -709,71 +604,35 @@
req.send();
} else {
if (!config['url']) {
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.match(/.*keycloak\.js/)) {
config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
break;
}
}
}
if (!config.realm) {
throw 'realm missing';
}
if (!config.clientId) {
throw 'clientId missing';
}
kc.authServerUrl = config.url;
kc.realm = config.realm;
kc.clientId = config.clientId;
kc.clientSecret = (config.credentials || {}).secret;
var oidcProvider = config['oidcProvider'];
if (!oidcProvider) {
if (!config['url']) {
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
if (scripts[i].src.match(/.*keycloak\.js/)) {
config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js'));
break;
}
}
}
if (!config.realm) {
throw 'realm missing';
}
kc.authServerUrl = config.url;
kc.realm = config.realm;
setupOidcEndoints(null);
promise.setSuccess();
} else {
if (typeof oidcProvider === 'string') {
var oidcProviderConfigUrl;
if (oidcProvider.charAt(oidcProvider.length - 1) == '/') {
oidcProviderConfigUrl = oidcProvider + '.well-known/openid-configuration';
} else {
oidcProviderConfigUrl = oidcProvider + '/.well-known/openid-configuration';
}
var req = new XMLHttpRequest();
req.open('GET', oidcProviderConfigUrl, true);
req.setRequestHeader('Accept', 'application/json');
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200 || fileLoaded(req)) {
var oidcProviderConfig = JSON.parse(req.responseText);
setupOidcEndoints(oidcProviderConfig);
promise.setSuccess();
} else {
promise.setError();
}
}
};
req.send();
} else {
setupOidcEndoints(oidcProvider);
promise.setSuccess();
}
}
promise.setSuccess();
}
return promise.promise;
}
function fileLoaded(xhr) {
return xhr.status == 0 && xhr.responseText && xhr.responseURL.startsWith('file:');
}
function setToken(token, refreshToken, idToken, timeLocal) {
if (kc.tokenTimeoutHandle) {
clearTimeout(kc.tokenTimeoutHandle);
@@ -883,137 +742,23 @@
}
function parseCallback(url) {
var oauth = parseCallbackUrl(url);
if (!oauth) {
return;
}
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
var oauthState = callbackStorage.get(oauth.state);
if (oauthState) {
oauth.valid = true;
if (oauthState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token)) {
oauth.redirectUri = oauthState.redirectUri;
oauth.storedNonce = oauthState.nonce;
oauth.prompt = oauthState.prompt;
}
return oauth;
}
function parseCallbackUrl(url) {
var supportedParams;
switch (kc.flow) {
case 'standard':
supportedParams = ['code', 'state', 'session_state'];
break;
case 'implicit':
supportedParams = ['access_token', 'id_token', 'state', 'session_state'];
break;
case 'hybrid':
supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state'];
break;
}
supportedParams.push('error');
supportedParams.push('error_description');
supportedParams.push('error_uri');
var queryIndex = url.indexOf('?');
var fragmentIndex = url.indexOf('#');
var newUrl;
var parsed;
if (kc.responseMode === 'query' && queryIndex !== -1) {
newUrl = url.substring(0, queryIndex);
parsed = parseCallbackParams(url.substring(queryIndex + 1, fragmentIndex !== -1 ? fragmentIndex : url.length), supportedParams);
if (parsed.paramsString !== '') {
newUrl += '?' + parsed.paramsString;
if (oauth.fragment) {
oauth.newUrl += '#' + oauth.fragment;
}
if (fragmentIndex !== -1) {
newUrl += url.substring(fragmentIndex);
}
} else if (kc.responseMode === 'fragment' && fragmentIndex !== -1) {
newUrl = url.substring(0, fragmentIndex);
parsed = parseCallbackParams(url.substring(fragmentIndex + 1), supportedParams);
if (parsed.paramsString !== '') {
newUrl += '#' + parsed.paramsString;
}
}
if (parsed && parsed.oauthParams) {
if (kc.flow === 'standard' || kc.flow === 'hybrid') {
if ((parsed.oauthParams.code || parsed.oauthParams.error) && parsed.oauthParams.state) {
parsed.oauthParams.newUrl = newUrl;
return parsed.oauthParams;
}
} else if (kc.flow === 'implicit') {
if ((parsed.oauthParams.access_token || parsed.oauthParams.error) && parsed.oauthParams.state) {
parsed.oauthParams.newUrl = newUrl;
return parsed.oauthParams;
}
}
return oauth;
}
}
function parseCallbackParams(paramsString, supportedParams) {
var p = paramsString.split('&');
var result = {
paramsString: '',
oauthParams: {}
}
for (var i = 0; i < p.length; i++) {
var t = p[i].split('=');
if (supportedParams.indexOf(t[0]) !== -1) {
result.oauthParams[t[0]] = t[1];
} else {
if (result.paramsString !== '') {
result.paramsString += '&';
}
result.paramsString += p[i];
}
}
return result;
}
function createPromise() {
if (typeof Promise === "function") {
return createNativePromise();
} else {
return createLegacyPromise();
}
}
function createNativePromise() {
// Need to create a native Promise which also preserves the
// interface of the custom promise type previously used by the API
var p = {
setSuccess: function(result) {
p.success = true;
p.resolve(result);
},
setError: function(result) {
p.success = false;
p.reject(result);
}
};
p.promise = new Promise(function(resolve, reject) {
p.resolve = resolve;
p.reject = reject;
});
p.promise.success = function(callback) {
p.promise.then(callback);
return p.promise;
}
p.promise.error = function(callback) {
p.promise.catch(callback);
return p.promise;
}
return p;
}
function createLegacyPromise() {
var p = {
setSuccess: function(result) {
p.success = true;
@@ -1070,34 +815,28 @@
loginIframe.iframe = iframe;
iframe.onload = function() {
var authUrl = kc.endpoints.authorize();
if (authUrl.charAt(0) === '/') {
var realmUrl = getRealmUrl();
if (realmUrl.charAt(0) === '/') {
loginIframe.iframeOrigin = getOrigin();
} else {
loginIframe.iframeOrigin = authUrl.substring(0, authUrl.indexOf('/', 8));
loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8));
}
promise.setSuccess();
setTimeout(check, loginIframe.interval * 1000);
}
var src = kc.endpoints.checkSessionIframe();
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
iframe.setAttribute('src', src );
iframe.setAttribute('title', 'keycloak-session-iframe' );
iframe.style.display = 'none';
document.body.appendChild(iframe);
var messageCallback = function(event) {
if ((event.origin !== loginIframe.iframeOrigin) || (loginIframe.iframe.contentWindow !== event.source)) {
if (event.origin !== loginIframe.iframeOrigin) {
return;
}
if (!(event.data == 'unchanged' || event.data == 'changed' || event.data == 'error')) {
return;
}
if (event.data != 'unchanged') {
if (event.data != "unchanged") {
kc.clearToken();
}
@@ -1105,7 +844,7 @@
for (var i = callbacks.length - 1; i >= 0; --i) {
var promise = callbacks[i];
if (event.data == 'unchanged') {
if (event.data == "unchanged") {
promise.setSuccess();
} else {
promise.setError();
@@ -1161,12 +900,7 @@
},
accountManagement : function() {
var accountUrl = kc.createAccountUrl();
if (typeof accountUrl !== 'undefined') {
window.location.href = accountUrl;
} else {
throw "Not supported by the OIDC server";
}
window.location.href = kc.createAccountUrl();
return createPromise().promise;
},
@@ -1180,7 +914,12 @@
} else if (kc.redirectUri) {
return kc.redirectUri;
} else {
return location.href;
var redirectUri = location.href;
if (location.hash && encodeHash) {
redirectUri = redirectUri.substring(0, location.href.indexOf('#'));
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1));
}
return redirectUri;
}
}
};
@@ -1188,62 +927,26 @@
if (type == 'cordova') {
loginIframe.enable = false;
var cordovaOpenWindowWrapper = function(loginUrl, target, options) {
if (window.cordova && window.cordova.InAppBrowser) {
// Use inappbrowser for IOS and Android if available
return window.cordova.InAppBrowser.open(loginUrl, target, options);
} else {
return window.open(loginUrl, target, options);
}
};
var shallowCloneCordovaOptions = function (userOptions) {
if (userOptions && userOptions.cordovaOptions) {
return Object.keys(userOptions.cordovaOptions).reduce(function (options, optionName) {
options[optionName] = userOptions.cordovaOptions[optionName];
return options;
}, {});
} else {
return {};
}
};
var formatCordovaOptions = function (cordovaOptions) {
return Object.keys(cordovaOptions).reduce(function (options, optionName) {
options.push(optionName+"="+cordovaOptions[optionName]);
return options;
}, []).join(",");
};
var createCordovaOptions = function (userOptions) {
var cordovaOptions = shallowCloneCordovaOptions(userOptions);
cordovaOptions.location = 'no';
if (userOptions && userOptions.prompt == 'none') {
cordovaOptions.hidden = 'yes';
}
return formatCordovaOptions(cordovaOptions);
};
return {
login: function(options) {
var promise = createPromise();
var cordovaOptions = createCordovaOptions(options);
var o = 'location=no';
if (options && options.prompt == 'none') {
o += ',hidden=yes';
}
var loginUrl = kc.createLoginUrl(options);
var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', cordovaOptions);
var ref = window.open(loginUrl, '_blank', o);
var completed = false;
var closed = false;
var closeBrowser = function() {
closed = true;
ref.close();
};
ref.addEventListener('loadstart', function(event) {
if (event.url.indexOf('http://localhost') == 0) {
var callback = parseCallback(event.url);
processCallback(callback, promise);
closeBrowser();
ref.close();
completed = true;
}
});
@@ -1253,31 +956,23 @@
if (event.url.indexOf('http://localhost') == 0) {
var callback = parseCallback(event.url);
processCallback(callback, promise);
closeBrowser();
ref.close();
completed = true;
} else {
promise.setError();
closeBrowser();
ref.close();
}
}
});
ref.addEventListener('exit', function(event) {
if (!closed) {
promise.setError({
reason: "closed_by_user"
});
}
});
return promise.promise;
},
logout: function(options) {
var promise = createPromise();
var logoutUrl = kc.createLogoutUrl(options);
var ref = cordovaOpenWindowWrapper(logoutUrl, '_blank', 'location=no,hidden=yes');
var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes');
var error;
@@ -1310,8 +1005,7 @@
register : function() {
var registerUrl = kc.createRegisterUrl();
var cordovaOptions = createCordovaOptions(options);
var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', cordovaOptions);
var ref = window.open(registerUrl, '_blank', 'location=no');
ref.addEventListener('loadstart', function(event) {
if (event.url.indexOf('http://localhost') == 0) {
ref.close();
@@ -1321,16 +1015,12 @@
accountManagement : function() {
var accountUrl = kc.createAccountUrl();
if (typeof accountUrl !== 'undefined') {
var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no');
ref.addEventListener('loadstart', function(event) {
if (event.url.indexOf('http://localhost') == 0) {
ref.close();
}
});
} else {
throw "Not supported by the OIDC server";
}
var ref = window.open(accountUrl, '_blank', 'location=no');
ref.addEventListener('loadstart', function(event) {
if (event.url.indexOf('http://localhost') == 0) {
ref.close();
}
});
},
redirectUri: function(options) {
@@ -1460,6 +1150,99 @@
return new CookieStorage();
}
var CallbackParser = function(uriToParse, responseMode) {
if (!(this instanceof CallbackParser)) {
return new CallbackParser(uriToParse, responseMode);
}
var parser = this;
var initialParse = function() {
var baseUri = null;
var queryString = null;
var fragmentString = null;
var questionMarkIndex = uriToParse.indexOf("?");
var fragmentIndex = uriToParse.indexOf("#", questionMarkIndex + 1);
if (questionMarkIndex == -1 && fragmentIndex == -1) {
baseUri = uriToParse;
} else if (questionMarkIndex != -1) {
baseUri = uriToParse.substring(0, questionMarkIndex);
queryString = uriToParse.substring(questionMarkIndex + 1);
if (fragmentIndex != -1) {
fragmentIndex = queryString.indexOf("#");
fragmentString = queryString.substring(fragmentIndex + 1);
queryString = queryString.substring(0, fragmentIndex);
}
} else {
baseUri = uriToParse.substring(0, fragmentIndex);
fragmentString = uriToParse.substring(fragmentIndex + 1);
}
return { baseUri: baseUri, queryString: queryString, fragmentString: fragmentString };
}
var parseParams = function(paramString) {
var result = {};
var params = paramString.split('&');
for (var i = 0; i < params.length; i++) {
var p = params[i].split('=');
var paramName = decodeURIComponent(p[0]);
var paramValue = decodeURIComponent(p[1]);
result[paramName] = paramValue;
}
return result;
}
var handleQueryParam = function(paramName, paramValue, oauth) {
var supportedOAuthParams = [ 'code', 'state', 'error', 'error_description' ];
for (var i = 0 ; i< supportedOAuthParams.length ; i++) {
if (paramName === supportedOAuthParams[i]) {
oauth[paramName] = paramValue;
return true;
}
}
return false;
}
parser.parseUri = function() {
var parsedUri = initialParse();
var queryParams = {};
if (parsedUri.queryString) {
queryParams = parseParams(parsedUri.queryString);
}
var oauth = { newUrl: parsedUri.baseUri };
for (var param in queryParams) {
switch (param) {
case 'redirect_fragment':
oauth.fragment = queryParams[param];
break;
default:
if (responseMode != 'query' || !handleQueryParam(param, queryParams[param], oauth)) {
oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + param + '=' + queryParams[param];
}
break;
}
}
if (responseMode === 'fragment') {
var fragmentParams = {};
if (parsedUri.fragmentString) {
fragmentParams = parseParams(parsedUri.fragmentString);
}
for (var param in fragmentParams) {
oauth[param] = fragmentParams[param];
}
}
return oauth;
}
}
}
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
@@ -0,0 +1,49 @@
<!--
~ 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.
-->
<script>
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
function receiveMessage(event)
{
if (event.origin !== "ORIGIN") {
console.log(event.origin + " does not match built origin");
return;
}
var data = JSON.parse(event.data);
data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
}
event.source.postMessage(JSON.stringify(data),
event.origin);
}
window.addEventListener("message", receiveMessage, false);
</script>
@@ -28,7 +28,7 @@
} else if (!init) {
var req = new XMLHttpRequest();
var url = location.href.split("?")[0] + "/init";
var url = location.href + "/init";
url += "?client_id=" + encodeURIComponent(clientId);
url += "&origin=" + encodeURIComponent(origin);
@@ -36,7 +36,7 @@
req.onreadystatechange = function () {
if (req.readyState === 4) {
if (req.status === 204 || req.status === 1223) {
if (req.status === 204) {
init = {
clientId: clientId,
origin: origin
@@ -79,10 +79,6 @@
function receiveMessage(event)
{
if (typeof event.data !== 'string') {
return
}
var origin = event.origin;
var data = event.data.split(' ');
if (data.length != 2) {
Loaded 100 of 7693 files, more files were not shown because too many files have changed in this diff. Show more