Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
242dc980fc | ||
|
|
b61ffa95cc | ||
|
|
a809fd2973 | ||
|
|
da82baec4a | ||
|
|
141ffc0ee0 | ||
|
|
0828136f01 |
No files matched your search
@@ -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
-11
@@ -1,28 +1,33 @@
|
||||
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=old
|
||||
- TESTS=crossdc
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
install: true
|
||||
before_script:
|
||||
- export MAVEN_SKIP_RC=true
|
||||
|
||||
script:
|
||||
install:
|
||||
- travis_wait 60 mvn install --no-snapshot-updates -Pdistribution -DskipTests=true -B -V -q
|
||||
|
||||
script:
|
||||
- ./travis-run-tests.sh $TESTS
|
||||
|
||||
sudo: false
|
||||
@@ -38,21 +38,17 @@ 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
|
||||
----------------------
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-4
@@ -101,7 +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();
|
||||
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)) {
|
||||
@@ -125,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());
|
||||
}
|
||||
|
||||
+1
-6
@@ -164,12 +164,7 @@ public class BearerTokenRequestAuthenticator {
|
||||
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";
|
||||
}
|
||||
+3
-3
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder disableCookieCache(boolean disable) {
|
||||
this.disableCookieCache = disable;
|
||||
public HttpClientBuilder disableCookieCache() {
|
||||
this.disableCookieCache = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -334,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) {
|
||||
|
||||
-39
@@ -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 {
|
||||
@@ -75,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;
|
||||
@@ -90,11 +87,6 @@ public class KeycloakDeployment {
|
||||
|
||||
// https://tools.ietf.org/html/rfc7636
|
||||
protected boolean pkce = false;
|
||||
protected boolean ignoreOAuthQueryParameter;
|
||||
|
||||
protected Map<String, String> redirectRewriteRules;
|
||||
|
||||
protected boolean delegateBearerErrorResponseSending = false;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
@@ -333,14 +325,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;
|
||||
}
|
||||
@@ -443,27 +427,4 @@ public class KeycloakDeployment {
|
||||
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;
|
||||
}
|
||||
}
|
||||
-5
@@ -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 {
|
||||
@@ -98,7 +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
|
||||
@@ -115,8 +112,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");
|
||||
|
||||
+6
-27
@@ -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;
|
||||
|
||||
|
||||
/**
|
||||
@@ -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) {
|
||||
@@ -173,7 +170,7 @@ public class OAuthRequestAuthenticator {
|
||||
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){
|
||||
@@ -323,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);
|
||||
@@ -379,23 +375,6 @@ public class OAuthRequestAuthenticator {
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-5
@@ -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()) {
|
||||
|
||||
+1
-3
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -78,13 +78,13 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
return createAuthorizationContext(accessToken);
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
return createAuthorizationContext(accessToken, null);
|
||||
return createAuthorizationContext(accessToken);
|
||||
}
|
||||
|
||||
handleAccessDenied(httpFacade);
|
||||
@@ -100,7 +100,7 @@ public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
if (isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken, pathConfig);
|
||||
return createAuthorizationContext(accessToken);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + pathConfig.getPath() + "].", e);
|
||||
}
|
||||
@@ -252,8 +252,8 @@ public abstract class AbstractPolicyEnforcer {
|
||||
return requiredScopes;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken, PathConfig pathConfig) {
|
||||
return new ClientAuthorizationContext(accessToken, pathConfig, this.paths, authzClient);
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||
return new ClientAuthorizationContext(accessToken, this.paths, authzClient);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
|
||||
+4
-4
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -28,6 +26,8 @@ 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>
|
||||
*/
|
||||
@@ -52,7 +52,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
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 + "\"");
|
||||
@@ -65,7 +65,7 @@ public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
|
||||
String clientId = authzClient.getConfiguration().getResource();
|
||||
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 + "\"");
|
||||
|
||||
+2
-2
@@ -127,7 +127,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getResource());
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
@@ -137,7 +137,7 @@ public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
||||
request.addPermission(permissionRequest);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getResource(), request);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ class PathMatcher {
|
||||
pathString = "/";
|
||||
}
|
||||
|
||||
if (matchingUri.equals(targetUri) || pathString.equals(targetUri)) {
|
||||
if (matchingUri.equals(targetUri)) {
|
||||
cache.put(targetUri, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
+2
-19
@@ -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 {
|
||||
|
||||
-6
@@ -29,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 {
|
||||
|
||||
@@ -56,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());
|
||||
@@ -75,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
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:brad.culley@spartasystems.com">Brad Culley</a>
|
||||
* @author <a href="mailto:john.ament@spartasystems.com">John D. Ament</a>
|
||||
*/
|
||||
public class KeycloakDeploymentTest {
|
||||
@Test
|
||||
public void shouldNotEnableOAuthQueryParamWhenIgnoreIsTrue() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(true);
|
||||
assertFalse(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreIsFalse() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
keycloakDeployment.setIgnoreOAuthQueryParameter(false);
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEnableOAuthQueryParamWhenIgnoreNotSet() {
|
||||
KeycloakDeployment keycloakDeployment = new KeycloakDeployment();
|
||||
|
||||
assertTrue(keycloakDeployment.isOAuthQueryParameterEnabled());
|
||||
}
|
||||
}
|
||||
@@ -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>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-13
@@ -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);
|
||||
}
|
||||
}
|
||||
+1
-9
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-as7-integration-pom</artifactId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
CLI Single Sign On
|
||||
===================================
|
||||
|
||||
This java-based utility is meant for providing Keycloak integration to
|
||||
command line applications that are either written in Java or another language. The
|
||||
idea is that the Java app provided by this utility performs a login for a specific
|
||||
client, parses responses, and exports an access token as an environment variable
|
||||
that can be used by the command line utility you are accessing.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/sh
|
||||
export KC_AUTH_SERVER=http://localhost:8080/auth
|
||||
export KC_REALM=master
|
||||
export KC_CLIENT=cli
|
||||
|
||||
export KC_ACCESS_TOKEN=`java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar login`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
java -DKEYCLOAK_AUTH_SERVER=$KC_AUTH_SERVER -DKEYCLOAK_REALM=$KC_REALM -DKEYCLOAK_CLIENT=$KC_CLIENT -jar target/keycloak-cli-sso-3.3.0.CR1-SNAPSHOT.jar logout
|
||||
|
||||
unset KC_ACCESS_TOKEN
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
~ and other contributors as indicated by the @author tags.
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-cli-sso</artifactId>
|
||||
<name>Keycloak CLI SSO Framework</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-installed-adapter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.keycloak.adapters.KeycloakCliSsoMain</mainClass>
|
||||
</transformer>
|
||||
</transformers>
|
||||
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.keycloak.adapters.installed.KeycloakCliSso;
|
||||
import org.keycloak.adapters.installed.KeycloakInstalled;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakCliSsoMain extends KeycloakCliSso {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
new KeycloakCliSsoMain().mainCmd(args);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-266
@@ -1,266 +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.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.ServerRequest;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakCliSso {
|
||||
|
||||
public void mainCmd(String[] args) throws Exception {
|
||||
if (args.length != 1) {
|
||||
printHelp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (args[0].equalsIgnoreCase("login")) {
|
||||
login();
|
||||
} else if (args[0].equalsIgnoreCase("login-manual")) {
|
||||
loginManual();
|
||||
} else if (args[0].equalsIgnoreCase("token")) {
|
||||
token();
|
||||
} else if (args[0].equalsIgnoreCase("logout")) {
|
||||
logout();
|
||||
} else if (args[0].equalsIgnoreCase("env")) {
|
||||
System.out.println(System.getenv().toString());
|
||||
} else {
|
||||
printHelp();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void printHelp() {
|
||||
System.err.println("Commands:");
|
||||
System.err.println(" login - login with desktop browser if available, otherwise do manual login. Output is access token.");
|
||||
System.err.println(" login-manual - manual login");
|
||||
System.err.println(" token - print access token if logged in");
|
||||
System.err.println(" logout - logout.");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
public AdapterConfig getConfig() {
|
||||
String url = System.getProperty("KEYCLOAK_AUTH_SERVER");
|
||||
if (url == null) {
|
||||
System.err.println("KEYCLOAK_AUTH_SERVER property not set");
|
||||
System.exit(1);
|
||||
}
|
||||
String realm = System.getProperty("KEYCLOAK_REALM");
|
||||
if (realm == null) {
|
||||
System.err.println("KEYCLOAK_REALM property not set");
|
||||
System.exit(1);
|
||||
|
||||
}
|
||||
String client = System.getProperty("KEYCLOAK_CLIENT");
|
||||
if (client == null) {
|
||||
System.err.println("KEYCLOAK_CLIENT property not set");
|
||||
System.exit(1);
|
||||
}
|
||||
String secret = System.getProperty("KEYCLOAK_CLIENT_SECRET");
|
||||
|
||||
|
||||
|
||||
AdapterConfig config = new AdapterConfig();
|
||||
config.setAuthServerUrl(url);
|
||||
config.setRealm(realm);
|
||||
config.setResource(client);
|
||||
config.setSslRequired("external");
|
||||
if (secret != null) {
|
||||
Map<String, Object> creds = new HashMap<>();
|
||||
creds.put("secret", secret);
|
||||
config.setCredentials(creds);
|
||||
} else {
|
||||
config.setPublicClient(true);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public boolean checkToken() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token == null) return false;
|
||||
|
||||
|
||||
if (token != null) {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
return true;
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.refreshToken(tokenResponse.getRefreshToken());
|
||||
processResponse(installed);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error processing existing token");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private String getTokenResponse() throws IOException {
|
||||
String token = null;
|
||||
File tokenFile = getTokenFilePath();
|
||||
if (tokenFile.exists()) {
|
||||
FileInputStream fis = new FileInputStream(tokenFile);
|
||||
byte[] data = new byte[(int) tokenFile.length()];
|
||||
fis.read(data);
|
||||
fis.close();
|
||||
token = new String(data, "UTF-8");
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
public void token() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token == null) {
|
||||
System.err.println("There is no token for client");
|
||||
System.exit(1);
|
||||
} else {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() < tokenResponse.getExpiresIn()) {
|
||||
System.out.println(tokenResponse.getToken());
|
||||
return;
|
||||
} else {
|
||||
System.err.println("token in response file is expired");
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure processing token response file");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Could not find json within token response file");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void login() throws Exception {
|
||||
if (checkToken()) return;
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakInstalled installed = new KeycloakInstalled(KeycloakDeploymentBuilder.build(config));
|
||||
installed.login();
|
||||
processResponse(installed);
|
||||
}
|
||||
|
||||
public String getHome() {
|
||||
String home = System.getenv("HOME");
|
||||
if (home == null) {
|
||||
home = System.getProperty("HOME");
|
||||
if (home == null) {
|
||||
home = Paths.get("").toAbsolutePath().normalize().toString();
|
||||
}
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
public File getTokenDirectory() {
|
||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM")).toFile();
|
||||
}
|
||||
|
||||
public File getTokenFilePath() {
|
||||
return Paths.get(getHome(), System.getProperty("basepath", ".keycloak-sso"), System.getProperty("KEYCLOAK_REALM"), System.getProperty("KEYCLOAK_CLIENT") + ".json").toFile();
|
||||
}
|
||||
|
||||
private void processResponse(KeycloakInstalled installed) throws IOException {
|
||||
AccessTokenResponse tokenResponse = installed.getTokenResponse();
|
||||
tokenResponse.setExpiresIn(Time.currentTime() + tokenResponse.getExpiresIn());
|
||||
tokenResponse.setIdToken(null);
|
||||
String output = JsonSerialization.writeValueAsString(tokenResponse);
|
||||
getTokenDirectory().mkdirs();
|
||||
FileOutputStream fos = new FileOutputStream(getTokenFilePath());
|
||||
fos.write(output.getBytes("UTF-8"));
|
||||
fos.flush();
|
||||
fos.close();
|
||||
System.out.println(tokenResponse.getToken());
|
||||
}
|
||||
|
||||
public void loginManual() throws Exception {
|
||||
if (checkToken()) return;
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
||||
KeycloakInstalled installed = new KeycloakInstalled(deployment);
|
||||
installed.loginManual();
|
||||
processResponse(installed);
|
||||
}
|
||||
|
||||
public void logout() throws Exception {
|
||||
String token = getTokenResponse();
|
||||
if (token != null) {
|
||||
Matcher m = Pattern.compile("\\{.*\\}\\z").matcher(token);
|
||||
if (m.find()) {
|
||||
String json = m.group(0);
|
||||
try {
|
||||
AccessTokenResponse tokenResponse = JsonSerialization.readValue(json, AccessTokenResponse.class);
|
||||
if (Time.currentTime() > tokenResponse.getExpiresIn()) {
|
||||
System.err.println("Login is expired");
|
||||
System.exit(1);
|
||||
}
|
||||
AdapterConfig config = getConfig();
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(config);
|
||||
ServerRequest.invokeLogout(deployment, tokenResponse.getRefreshToken());
|
||||
for (File fp : getTokenDirectory().listFiles()) fp.delete();
|
||||
System.out.println("logout complete");
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure processing token response file");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Could not find json within token response file");
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.err.println("Not logged in");
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-139
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.keycloak.adapters.installed;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
@@ -25,7 +24,6 @@ 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;
|
||||
@@ -45,7 +43,6 @@ 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;
|
||||
|
||||
@@ -54,11 +51,6 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
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;
|
||||
@@ -67,18 +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;
|
||||
|
||||
|
||||
|
||||
public KeycloakInstalled() {
|
||||
InputStream config = Thread.currentThread().getContextClassLoader().getResourceAsStream(KEYCLOAK_JSON);
|
||||
@@ -89,92 +75,6 @@ public class KeycloakInstalled {
|
||||
deployment = KeycloakDeploymentBuilder.build(config);
|
||||
}
|
||||
|
||||
public KeycloakInstalled(KeycloakDeployment deployment) {
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
private static HttpResponseWriter defaultLoginWriter = new HttpResponseWriter() {
|
||||
@Override
|
||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Login completed.</h1><div>");
|
||||
pw.println("This browser will remain logged in until you close it, logout, or the session expires.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Login attempt failed.</h1><div>");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
};
|
||||
private static HttpResponseWriter defaultLogoutWriter = new HttpResponseWriter() {
|
||||
@Override
|
||||
public void success(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Logout completed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failure(PrintWriter pw, KeycloakInstalled ki) {
|
||||
pw.println("HTTP/1.1 200 OK");
|
||||
pw.println("Content-Type: text/html");
|
||||
pw.println();
|
||||
pw.println("<html><h1>Logout failed.</h1><div>");
|
||||
pw.println("You may close this browser tab.");
|
||||
pw.println("</div></html>");
|
||||
pw.flush();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
public HttpResponseWriter getLoginResponseWriter() {
|
||||
if (loginResponseWriter == null) {
|
||||
return defaultLoginWriter;
|
||||
} else {
|
||||
return loginResponseWriter;
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponseWriter getLogoutResponseWriter() {
|
||||
if (logoutResponseWriter == null) {
|
||||
return defaultLogoutWriter;
|
||||
} else {
|
||||
return logoutResponseWriter;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLoginResponseWriter(HttpResponseWriter loginResponseWriter) {
|
||||
this.loginResponseWriter = loginResponseWriter;
|
||||
}
|
||||
|
||||
public void setLogoutResponseWriter(HttpResponseWriter logoutResponseWriter) {
|
||||
this.logoutResponseWriter = logoutResponseWriter;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public void login() throws IOException, ServerRequest.HttpFailure, VerificationException, InterruptedException, OAuthErrorException, URISyntaxException {
|
||||
if (isDesktopSupported()) {
|
||||
loginDesktop();
|
||||
@@ -208,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));
|
||||
|
||||
@@ -247,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();
|
||||
@@ -270,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()
|
||||
@@ -308,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();
|
||||
@@ -347,10 +240,6 @@ public class KeycloakInstalled {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public AccessTokenResponse getTokenResponse() {
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
public boolean isDesktopSupported() {
|
||||
return Desktop.isDesktopSupported();
|
||||
}
|
||||
@@ -359,8 +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);
|
||||
@@ -382,7 +269,6 @@ public class KeycloakInstalled {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public class CallbackListener extends Thread {
|
||||
|
||||
private ServerSocket server;
|
||||
@@ -397,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();
|
||||
@@ -433,15 +314,10 @@ public class KeycloakInstalled {
|
||||
}
|
||||
}
|
||||
|
||||
OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
|
||||
PrintWriter pw = new PrintWriter(out);
|
||||
|
||||
if (error == null) {
|
||||
writer.success(pw, KeycloakInstalled.this);
|
||||
} else {
|
||||
writer.failure(pw, KeycloakInstalled.this);
|
||||
}
|
||||
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;
|
||||
@@ -452,8 +328,6 @@ public class KeycloakInstalled {
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Jetty Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.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>
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import * as Keycloak from 'keycloak';
|
||||
|
||||
export as namespace KeycloakAuthorization;
|
||||
|
||||
export = KeycloakAuthorization;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
|
||||
|
||||
declare namespace KeycloakAuthorization {
|
||||
interface KeycloakAuthorizationPromise {
|
||||
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
|
||||
}
|
||||
|
||||
interface KeycloakAuthorizationInstance {
|
||||
rpt: any;
|
||||
config: { rpt_endpoint: string };
|
||||
|
||||
init(): void;
|
||||
|
||||
/**
|
||||
* This method enables client applications to better integrate with resource servers protected by a Keycloak
|
||||
* policy enforcer.
|
||||
*
|
||||
* In this case, the resource server will respond with a 401 status code and a WWW-Authenticate header holding the
|
||||
* necessary information to ask a Keycloak server for authorization data using both UMA and Entitlement protocol,
|
||||
* depending on how the policy enforcer at the resource server was configured.
|
||||
*/
|
||||
authorize(wwwAuthenticateHeader: string): KeycloakAuthorizationPromise;
|
||||
|
||||
/**
|
||||
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
|
||||
*/
|
||||
entitlement(resourceServerId: string, entitlementRequest: {}): KeycloakAuthorizationPromise;
|
||||
}
|
||||
}
|
||||
+95
-472
@@ -1,478 +1,101 @@
|
||||
/*
|
||||
* MIT License
|
||||
* Copyright 2017 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
|
||||
* 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
|
||||
*
|
||||
* 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:
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
export as namespace Keycloak;
|
||||
|
||||
export = Keycloak;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
|
||||
declare namespace Keycloak {
|
||||
type KeycloakAdapterName = 'cordova'|'default';
|
||||
type KeycloakOnLoad = 'login-required'|'check-sso';
|
||||
type KeycloakResponseMode = 'query'|'fragment';
|
||||
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
|
||||
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
|
||||
|
||||
interface KeycloakInitOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
adapter?: KeycloakAdapterName;
|
||||
|
||||
/**
|
||||
* Specifies an action to do on load.
|
||||
*/
|
||||
onLoad?: KeycloakOnLoad;
|
||||
|
||||
/**
|
||||
* Set an initial value for the token.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the refresh token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the id token (only together with `token` or
|
||||
* `refreshToken`).
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for skew between local time and Keycloak server in
|
||||
* seconds (only together with `token` or `refreshToken`).
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* Set to enable/disable monitoring login state.
|
||||
* @default true
|
||||
*/
|
||||
checkLoginIframe?: boolean;
|
||||
|
||||
/**
|
||||
* Set the interval to check login state (in seconds).
|
||||
* @default 5
|
||||
*/
|
||||
checkLoginIframeInterval?: boolean;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect response mode to send to Keycloak upon login.
|
||||
* @default fragment After successful authentication Keycloak will redirect
|
||||
* to JavaScript application with OpenID Connect parameters
|
||||
* added in URL fragment. This is generally safer and
|
||||
* recommended over query.
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect flow.
|
||||
* @default standard
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
}
|
||||
|
||||
interface KeycloakLoginOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
scope?: string;
|
||||
|
||||
/**
|
||||
* Specifies the uri to redirect to after login.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* By default the login screen is displayed if the user is not logged into
|
||||
* Keycloak. To only authenticate to the application if the user is already
|
||||
* logged in and not display the login page if the user is not logged in, set
|
||||
* this option to `'none'`. To always require re-authentication and ignore
|
||||
* SSO, set this option to `'login'`.
|
||||
*/
|
||||
prompt?: 'none'|'login';
|
||||
|
||||
/**
|
||||
* If value is `'register'` then user is redirected to registration page,
|
||||
* otherwise to login page.
|
||||
*/
|
||||
action?: 'register';
|
||||
|
||||
/**
|
||||
* Used just if user is already authenticated. Specifies maximum time since
|
||||
* the authentication of user happened. If user is already authenticated for
|
||||
* longer time than `'maxAge'`, the SSO is ignored and he will need to
|
||||
* authenticate again.
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Used to pre-fill the username/email field on the login form.
|
||||
*/
|
||||
loginHint?: string;
|
||||
|
||||
/**
|
||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||
*/
|
||||
idpHint?: string;
|
||||
|
||||
/**
|
||||
* Specifies the desired locale for the UI.
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
|
||||
interface KeycloakPromise<TSuccess, TError> {
|
||||
/**
|
||||
* Function to call if the promised action succeeds.
|
||||
*/
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
|
||||
/**
|
||||
* Function to call if the promised action throws an error.
|
||||
*/
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
|
||||
interface KeycloakError {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
|
||||
interface KeycloakProfile {
|
||||
id?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
totp?: boolean;
|
||||
createdTimestamp?: number;
|
||||
}
|
||||
|
||||
// export interface KeycloakUserInfo {}
|
||||
|
||||
/**
|
||||
* A client for the Keycloak authentication server.
|
||||
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
|
||||
*/
|
||||
interface KeycloakInstance {
|
||||
/**
|
||||
* Is true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
authenticated?: boolean;
|
||||
|
||||
/**
|
||||
* The user id.
|
||||
*/
|
||||
subject?: string;
|
||||
|
||||
/**
|
||||
* Response mode passed in init (default value is `'fragment'`).
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Response type sent to Keycloak with login requests. This is determined
|
||||
* based on the flow value used during initialization, but can be overridden
|
||||
* by setting this value.
|
||||
*/
|
||||
responseType?: KeycloakResponseType;
|
||||
|
||||
/**
|
||||
* Flow passed in init.
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
|
||||
/**
|
||||
* The realm roles associated with the token.
|
||||
*/
|
||||
realmAccess?: { roles: string[] };
|
||||
|
||||
/**
|
||||
* The resource roles associated with the token.
|
||||
*/
|
||||
resourceAccess?: string[];
|
||||
|
||||
/**
|
||||
* The base64 encoded token that can be sent in the Authorization header in
|
||||
* requests to services.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The parsed token as a JavaScript object.
|
||||
*/
|
||||
tokenParsed?: {
|
||||
exp?: number;
|
||||
iat?: number;
|
||||
nonce?: string;
|
||||
sub?: string;
|
||||
session_state?: string;
|
||||
realm_access?: { roles: string[] };
|
||||
resource_access?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The base64 encoded refresh token that can be used to retrieve a new token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed refresh token as a JavaScript object.
|
||||
*/
|
||||
refreshTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The base64 encoded ID token.
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed id token as a JavaScript object.
|
||||
*/
|
||||
idTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The estimated time difference between the browser time and the Keycloak
|
||||
* server in seconds. This value is just an estimation, but is accurate
|
||||
* enough when determining if a token is expired or not.
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loginRequired?: boolean;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
authServerUrl?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
realm?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
profile?: KeycloakProfile;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
userInfo?: {}; // KeycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Called when the adapter is initialized.
|
||||
*/
|
||||
onReady?(authenticated?: boolean): void;
|
||||
|
||||
/**
|
||||
* Called when a user is successfully authenticated.
|
||||
*/
|
||||
onAuthSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error during authentication.
|
||||
*/
|
||||
onAuthError?(errorData: KeycloakError): void;
|
||||
|
||||
/**
|
||||
* Called when the token is refreshed.
|
||||
*/
|
||||
onAuthRefreshSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error while trying to refresh the token.
|
||||
*/
|
||||
onAuthRefreshError?(): void;
|
||||
|
||||
/**
|
||||
* Called if the user is logged out (will only be called if the session
|
||||
* status iframe is enabled, or in Cordova mode).
|
||||
*/
|
||||
onAuthLogout?(): void;
|
||||
|
||||
/**
|
||||
* Called when the access token is expired. If a refresh token is available
|
||||
* the token can be refreshed with Keycloak#updateToken, or in cases where
|
||||
* it's not (ie. with implicit flow) you can redirect to login screen to
|
||||
* obtain a new access token.
|
||||
*/
|
||||
onTokenExpired?(): void;
|
||||
|
||||
/**
|
||||
* Called to initialize the adapter.
|
||||
* @param initOptions Initialization options.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
|
||||
|
||||
/**
|
||||
* Redirects to login form.
|
||||
* @param options Login options.
|
||||
*/
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to logout.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to registration form.
|
||||
* @param options Supports same options as Keycloak#login but `action` is
|
||||
* set to `'register'`.
|
||||
*/
|
||||
register(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to the Account Management Console.
|
||||
*/
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Returns the URL to login form.
|
||||
* @param options Supports same options as Keycloak#login.
|
||||
*/
|
||||
createLoginUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to logout the user.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
createLogoutUrl(options?: any): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to registration page.
|
||||
* @param options Supports same options as Keycloak#createLoginUrl but
|
||||
* `action` is set to `'register'`.
|
||||
*/
|
||||
createRegisterUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to the Account Management Console.
|
||||
*/
|
||||
createAccountUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the token has less than `minValidity` seconds left before
|
||||
* it expires.
|
||||
* @param minValidity If not specified, `0` is used.
|
||||
*/
|
||||
isTokenExpired(minValidity?: number): boolean;
|
||||
|
||||
/**
|
||||
* If the token expires within `minValidity` seconds, the token is refreshed.
|
||||
* If the session status iframe is enabled, the session status is also
|
||||
* checked.
|
||||
* @returns A promise to set functions that can be invoked if the token is
|
||||
* still valid, or if the token is no longer valid.
|
||||
* @example
|
||||
* ```js
|
||||
* keycloak.updateToken(5).success(function(refreshed) {
|
||||
* if (refreshed) {
|
||||
* alert('Token was successfully refreshed');
|
||||
* } else {
|
||||
* alert('Token is still valid');
|
||||
* }
|
||||
* }).error(function() {
|
||||
* alert('Failed to refresh the token, or the session has expired');
|
||||
* });
|
||||
*/
|
||||
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
|
||||
|
||||
/**
|
||||
* Clears authentication state, including tokens. This can be useful if
|
||||
* the application has detected the session was expired, for example if
|
||||
* updating token fails. Invoking this results in Keycloak#onAuthLogout
|
||||
* callback listener being invoked.
|
||||
*/
|
||||
clearToken(): void;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given realm role.
|
||||
* @param role A realm role name.
|
||||
*/
|
||||
hasRealmRole(role: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given role for the resource.
|
||||
* @param role A role name.
|
||||
* @param resource If not specified, `clientId` is used.
|
||||
*/
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
|
||||
/**
|
||||
* Loads the user's profile.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loadUserInfo(): KeycloakPromise<{}, void>;
|
||||
}
|
||||
declare module KeycloakModule {
|
||||
|
||||
export interface Promise {
|
||||
success(callback: Function): Promise;
|
||||
error(callback: Function): Promise;
|
||||
}
|
||||
|
||||
export type ResponseModes = "query" | "fragment";
|
||||
export type Flows = "standard" | "implicit" | "hybrid";
|
||||
|
||||
export interface InitOptions {
|
||||
checkLoginIframe?: boolean;
|
||||
checkLoginIframeInterval?: number;
|
||||
onLoad?: string;
|
||||
adapter?: string;
|
||||
responseMode?: ResponseModes;
|
||||
flow?: Flows;
|
||||
token?: string;
|
||||
refreshToken?: string;
|
||||
idToken?: string;
|
||||
timeSkew?: number;
|
||||
}
|
||||
|
||||
export interface LoginOptions {
|
||||
redirectUri?: string;
|
||||
prompt?: string;
|
||||
maxAge?: number;
|
||||
loginHint?: string;
|
||||
action?: string;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface RedirectUriOptions {
|
||||
redirectUri?: string;
|
||||
}
|
||||
|
||||
export interface KeycloakClient {
|
||||
init(options?: InitOptions): Promise;
|
||||
login(options?: LoginOptions): Promise;
|
||||
createLoginUrl(options?: LoginOptions): string;
|
||||
logout(options?: RedirectUriOptions): Promise;
|
||||
createLogoutUrl(options?: RedirectUriOptions): string;
|
||||
register(options?: LoginOptions): Promise;
|
||||
createRegisterUrl(options?: RedirectUriOptions): string;
|
||||
accountManagement(): Promise;
|
||||
createAccountUrl(options?: RedirectUriOptions): string;
|
||||
hasRealmRole(role: string): boolean;
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
loadUserProfile(): Promise;
|
||||
isTokenExpired(minValidity: number): boolean;
|
||||
updateToken(minValidity: number): Promise;
|
||||
clearToken(): any;
|
||||
|
||||
realm: string;
|
||||
clientId: string;
|
||||
authServerUrl: string;
|
||||
|
||||
token: string;
|
||||
tokenParsed: any;
|
||||
refreshToken: string;
|
||||
refreshTokenParsed: any;
|
||||
idToken: string;
|
||||
idTokenParsed: any;
|
||||
realmAccess: any;
|
||||
resourceAccess: any;
|
||||
authenticated: boolean;
|
||||
subject: string;
|
||||
timeSkew: number;
|
||||
responseMode: ResponseModes;
|
||||
flow: Flows;
|
||||
responseType: string;
|
||||
|
||||
onReady: Function;
|
||||
onAuthSuccess: Function;
|
||||
onAuthError: Function;
|
||||
onAuthRefreshSuccess: Function;
|
||||
onAuthRefreshError: Function;
|
||||
onAuthLogout: Function;
|
||||
onTokenExpired: Function;
|
||||
}
|
||||
}
|
||||
|
||||
declare var Keycloak: {
|
||||
new(config?: any): KeycloakModule.KeycloakClient;
|
||||
};
|
||||
@@ -33,13 +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];
|
||||
}
|
||||
}
|
||||
|
||||
kc.init = function (initOptions) {
|
||||
kc.authenticated = false;
|
||||
|
||||
@@ -50,7 +43,7 @@
|
||||
} else if (initOptions && initOptions.adapter === 'default') {
|
||||
adapter = loadAdapter();
|
||||
} else {
|
||||
if (window.Cordova || window.cordova) {
|
||||
if (window.Cordova) {
|
||||
adapter = loadAdapter('cordova');
|
||||
} else {
|
||||
adapter = loadAdapter();
|
||||
@@ -221,7 +214,7 @@
|
||||
var callbackState = {
|
||||
state: state,
|
||||
nonce: nonce,
|
||||
redirectUri: encodeURIComponent(redirectUri)
|
||||
redirectUri: encodeURIComponent(redirectUri),
|
||||
}
|
||||
|
||||
if (options && options.prompt) {
|
||||
@@ -594,7 +587,7 @@
|
||||
|
||||
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'];
|
||||
@@ -640,10 +633,6 @@
|
||||
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);
|
||||
@@ -838,12 +827,7 @@
|
||||
}
|
||||
|
||||
var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html';
|
||||
if (kc.iframeVersion) {
|
||||
src = src + '?version=' + kc.iframeVersion;
|
||||
}
|
||||
|
||||
iframe.setAttribute('src', src );
|
||||
iframe.setAttribute('title', 'keycloak-session-iframe' );
|
||||
iframe.style.display = 'none';
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
@@ -948,14 +932,7 @@
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
login: function(options) {
|
||||
var promise = createPromise();
|
||||
@@ -966,7 +943,8 @@
|
||||
}
|
||||
|
||||
var loginUrl = kc.createLoginUrl(options);
|
||||
var ref = cordovaOpenWindowWrapper(loginUrl, '_blank', o);
|
||||
var ref = window.open(loginUrl, '_blank', o);
|
||||
|
||||
var completed = false;
|
||||
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
@@ -999,7 +977,7 @@
|
||||
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;
|
||||
|
||||
@@ -1032,7 +1010,7 @@
|
||||
|
||||
register : function() {
|
||||
var registerUrl = kc.createRegisterUrl();
|
||||
var ref = cordovaOpenWindowWrapper(registerUrl, '_blank', 'location=no');
|
||||
var ref = window.open(registerUrl, '_blank', 'location=no');
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
if (event.url.indexOf('http://localhost') == 0) {
|
||||
ref.close();
|
||||
@@ -1042,7 +1020,7 @@
|
||||
|
||||
accountManagement : function() {
|
||||
var accountUrl = kc.createAccountUrl();
|
||||
var ref = cordovaOpenWindowWrapper(accountUrl, '_blank', 'location=no');
|
||||
var ref = window.open(accountUrl, '_blank', 'location=no');
|
||||
ref.addEventListener('loadstart', function(event) {
|
||||
if (event.url.indexOf('http://localhost') == 0) {
|
||||
ref.close();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak OIDC Client Adapter Modules</name>
|
||||
@@ -34,7 +34,6 @@
|
||||
<module>adapter-core</module>
|
||||
<module>as7-eap6</module>
|
||||
<module>installed</module>
|
||||
<module>cli-sso</module>
|
||||
<module>jaxrs-oauth-client</module>
|
||||
<module>jetty</module>
|
||||
<module>js</module>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+30
-54
@@ -54,96 +54,72 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
public class KeycloakOIDCFilter implements Filter {
|
||||
|
||||
private final static Logger log = Logger.getLogger("" + KeycloakOIDCFilter.class);
|
||||
|
||||
public static final String SKIP_PATTERN_PARAM = "keycloak.config.skipPattern";
|
||||
|
||||
public static final String CONFIG_RESOLVER_PARAM = "keycloak.config.resolver";
|
||||
|
||||
public static final String CONFIG_FILE_PARAM = "keycloak.config.file";
|
||||
|
||||
public static final String CONFIG_PATH_PARAM = "keycloak.config.path";
|
||||
|
||||
protected AdapterDeploymentContext deploymentContext;
|
||||
|
||||
protected SessionIdMapper idMapper = new InMemorySessionIdMapper();
|
||||
|
||||
protected NodesRegistrationManagement nodesRegistrationManagement;
|
||||
|
||||
protected Pattern skipPattern;
|
||||
|
||||
private final KeycloakConfigResolver definedconfigResolver;
|
||||
|
||||
/**
|
||||
* Constructor that can be used to define a {@code KeycloakConfigResolver} that will be used at initialization to
|
||||
* provide the {@code KeycloakDeployment}.
|
||||
* @param definedconfigResolver the resolver
|
||||
*/
|
||||
public KeycloakOIDCFilter(KeycloakConfigResolver definedconfigResolver) {
|
||||
this.definedconfigResolver = definedconfigResolver;
|
||||
}
|
||||
|
||||
public KeycloakOIDCFilter() {
|
||||
this(null);
|
||||
}
|
||||
private final static Logger log = Logger.getLogger(""+KeycloakOIDCFilter.class);
|
||||
|
||||
@Override
|
||||
public void init(final FilterConfig filterConfig) throws ServletException {
|
||||
|
||||
String skipPatternDefinition = filterConfig.getInitParameter(SKIP_PATTERN_PARAM);
|
||||
if (skipPatternDefinition != null) {
|
||||
skipPattern = Pattern.compile(skipPatternDefinition, Pattern.DOTALL);
|
||||
}
|
||||
|
||||
if (definedconfigResolver != null) {
|
||||
deploymentContext = new AdapterDeploymentContext(definedconfigResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", definedconfigResolver.getClass());
|
||||
String configResolverClass = filterConfig.getInitParameter("keycloak.config.resolver");
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
} else {
|
||||
String configResolverClass = filterConfig.getInitParameter(CONFIG_RESOLVER_PARAM);
|
||||
if (configResolverClass != null) {
|
||||
String fp = filterConfig.getInitParameter("keycloak.config.file");
|
||||
InputStream is = null;
|
||||
if (fp != null) {
|
||||
try {
|
||||
KeycloakConfigResolver configResolver = (KeycloakConfigResolver) getClass().getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
log.log(Level.INFO, "Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.FINE, "The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
is = new FileInputStream(fp);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
String fp = filterConfig.getInitParameter(CONFIG_FILE_PARAM);
|
||||
InputStream is = null;
|
||||
if (fp != null) {
|
||||
try {
|
||||
is = new FileInputStream(fp);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
String path = "/WEB-INF/keycloak.json";
|
||||
String pathParam = filterConfig.getInitParameter(CONFIG_PATH_PARAM);
|
||||
if (pathParam != null) path = pathParam;
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
}
|
||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.fine("Keycloak is using a per-deployment configuration.");
|
||||
String path = "/WEB-INF/keycloak.json";
|
||||
String pathParam = filterConfig.getInitParameter("keycloak.config.path");
|
||||
if (pathParam != null) path = pathParam;
|
||||
is = filterConfig.getServletContext().getResourceAsStream(path);
|
||||
}
|
||||
KeycloakDeployment kd = createKeycloakDeploymentFrom(is);
|
||||
deploymentContext = new AdapterDeploymentContext(kd);
|
||||
log.fine("Keycloak is using a per-deployment configuration.");
|
||||
}
|
||||
filterConfig.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
}
|
||||
|
||||
private KeycloakDeployment createKeycloakDeploymentFrom(InputStream is) {
|
||||
|
||||
if (is == null) {
|
||||
log.fine("No adapter configuration. Keycloak is unconfigured and will deny all requests.");
|
||||
return new KeycloakDeployment();
|
||||
}
|
||||
|
||||
return KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
|
||||
|
||||
log.fine("Keycloak OIDC Filter");
|
||||
//System.err.println("Keycloak OIDC Filter: " + ((HttpServletRequest)req).getRequestURL().toString());
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
||||
@@ -225,7 +201,7 @@ public class KeycloakOIDCFilter implements Filter {
|
||||
*
|
||||
* @param request the request to check
|
||||
* @return {@code true} if the request should not be handled,
|
||||
* {@code false} otherwise.
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
private boolean shouldSkip(HttpServletRequest request) {
|
||||
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
|
||||
HttpSession httpSession = request.getSession();
|
||||
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
|
||||
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
|
||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
|
||||
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
|
||||
//String username = securityContext.getToken().getSubject();
|
||||
//log.fine("userSessionManagement.login: " + username);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-boot-container-bundle</artifactId>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -31,9 +31,7 @@
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>1.4.0.RELEASE</spring-boot.version>
|
||||
<spring.version>4.1.6.RELEASE</spring.version>
|
||||
<mockito.version>1.9.5</mockito.version>
|
||||
<spring-boot.version>1.3.0.RELEASE</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -100,19 +98,7 @@
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
|
||||
-2
@@ -33,7 +33,6 @@ import org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve;
|
||||
import org.keycloak.adapters.undertow.KeycloakServletExtension;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
|
||||
@@ -61,7 +60,6 @@ import java.util.Set;
|
||||
@Configuration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(KeycloakSpringBootProperties.class)
|
||||
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
|
||||
public class KeycloakAutoConfiguration {
|
||||
|
||||
private KeycloakSpringBootProperties keycloakProperties;
|
||||
|
||||
-13
@@ -34,11 +34,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
@JsonIgnore
|
||||
private Map config = new HashMap();
|
||||
|
||||
/**
|
||||
* Allow enabling of Keycloak Spring Boot adapter by configuration.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
public Map getConfig() {
|
||||
return config;
|
||||
}
|
||||
@@ -48,14 +43,6 @@ public class KeycloakSpringBootProperties extends AdapterConfig {
|
||||
*/
|
||||
private List<SecurityConstraint> securityConstraints = new ArrayList<SecurityConstraint>();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This matches security-constraint of the servlet spec
|
||||
*/
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateCustomizer;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
public class KeycloakRestTemplateCustomizer implements RestTemplateCustomizer {
|
||||
|
||||
private final KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor;
|
||||
|
||||
public KeycloakRestTemplateCustomizer() {
|
||||
this(new KeycloakSecurityContextClientRequestInterceptor());
|
||||
}
|
||||
|
||||
protected KeycloakRestTemplateCustomizer(
|
||||
KeycloakSecurityContextClientRequestInterceptor keycloakInterceptor
|
||||
) {
|
||||
this.keycloakInterceptor = keycloakInterceptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(RestTemplate restTemplate) {
|
||||
restTemplate.getInterceptors().add(keycloakInterceptor);
|
||||
}
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.springframework.http.HttpRequest;
|
||||
import org.springframework.http.client.ClientHttpRequestExecution;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* Interceptor for {@link ClientHttpRequestExecution} objects created for server to server secured
|
||||
* communication using OAuth2 bearer tokens issued by Keycloak.
|
||||
*
|
||||
* @author <a href="mailto:jmcshan1@gmail.com">James McShane</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class KeycloakSecurityContextClientRequestInterceptor implements ClientHttpRequestInterceptor {
|
||||
|
||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
/**
|
||||
* Returns the {@link KeycloakSecurityContext} from the Spring {@link ServletRequestAttributes}'s {@link Principal}.
|
||||
*
|
||||
* The principal must support retrieval of the KeycloakSecurityContext, so at this point, only {@link KeycloakPrincipal}
|
||||
* values are supported
|
||||
*
|
||||
* @return the current <code>KeycloakSecurityContext</code>
|
||||
*/
|
||||
protected KeycloakSecurityContext getKeycloakSecurityContext() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
Principal principal = attributes.getRequest().getUserPrincipal();
|
||||
if (principal == null) {
|
||||
throw new IllegalStateException("Cannot set authorization header because there is no authenticated principal");
|
||||
}
|
||||
if (!(principal instanceof KeycloakPrincipal)) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Cannot set authorization header because the principal type %s does not provide the KeycloakSecurityContext",
|
||||
principal.getClass()));
|
||||
}
|
||||
return ((KeycloakPrincipal) principal).getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
|
||||
KeycloakSecurityContext context = this.getKeycloakSecurityContext();
|
||||
httpRequest.getHeaders().set(AUTHORIZATION_HEADER, "Bearer " + context.getTokenString());
|
||||
return clientHttpRequestExecution.execute(httpRequest, bytes);
|
||||
}
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package org.keycloak.adapters.springboot.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class KeycloakRestTemplateCustomizerTest {
|
||||
|
||||
private KeycloakRestTemplateCustomizer customizer;
|
||||
private KeycloakSecurityContextClientRequestInterceptor interceptor =
|
||||
mock(KeycloakSecurityContextClientRequestInterceptor.class);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
customizer = new KeycloakRestTemplateCustomizer(interceptor);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interceptorIsAddedToRequest() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
customizer.customize(restTemplate);
|
||||
assertTrue(restTemplate.getInterceptors().contains(interceptor));
|
||||
}
|
||||
|
||||
}
|
||||
-87
@@ -1,87 +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.springboot.client;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Keycloak spring boot client request factory tests.
|
||||
*/
|
||||
public class KeycloakSecurityContextClientRequestInterceptorTest {
|
||||
|
||||
@Spy
|
||||
private KeycloakSecurityContextClientRequestInterceptor factory;
|
||||
|
||||
private MockHttpServletRequest servletRequest;
|
||||
|
||||
@Mock
|
||||
private KeycloakSecurityContext keycloakSecurityContext;
|
||||
|
||||
@Mock
|
||||
private KeycloakPrincipal keycloakPrincipal;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
servletRequest = new MockHttpServletRequest();
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest));
|
||||
servletRequest.setUserPrincipal(keycloakPrincipal);
|
||||
when(keycloakPrincipal.getKeycloakSecurityContext()).thenReturn(keycloakSecurityContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetKeycloakSecurityContext() throws Exception {
|
||||
KeycloakSecurityContext context = factory.getKeycloakSecurityContext();
|
||||
assertNotNull(context);
|
||||
assertEquals(keycloakSecurityContext, context);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetKeycloakSecurityContextInvalidPrincipal() throws Exception {
|
||||
servletRequest.setUserPrincipal(new MarkerPrincipal());
|
||||
factory.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testGetKeycloakSecurityContextNullAuthentication() throws Exception {
|
||||
servletRequest.setUserPrincipal(null);
|
||||
factory.getKeycloakSecurityContext();
|
||||
}
|
||||
|
||||
private static class MarkerPrincipal implements Principal {
|
||||
@Override
|
||||
public String getName() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package org.keycloak.adapters.springsecurity;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Add this annotation to a class that extends {@code KeycloakWebSecurityConfigurerAdapter} to provide
|
||||
* a keycloak based Spring security configuration.
|
||||
*
|
||||
* @author Hendrik Ebbers
|
||||
*/
|
||||
@Retention(value = RUNTIME)
|
||||
@Target(value = { TYPE })
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
|
||||
@EnableWebSecurity
|
||||
public @interface KeycloakConfiguration {
|
||||
}
|
||||
-48
@@ -1,48 +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.springsecurity.authentication;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* To return the forbidden code with the corresponding message.
|
||||
*
|
||||
* @author emilienbondu
|
||||
*
|
||||
*/
|
||||
public class KeycloakAuthenticationFailureHandler implements AuthenticationFailureHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
|
||||
// Check that the response was not committed yet (this may happen when another
|
||||
// part of the Keycloak adapter sends a challenge or a redirect).
|
||||
if (!response.isCommitted()) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate using the Authorization header");
|
||||
} else {
|
||||
if (200 <= response.getStatus() && response.getStatus() < 300) {
|
||||
throw new RuntimeException("Success response was committed while authentication failed!", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -50,7 +50,7 @@ public class KeycloakAuthenticationProvider implements AuthenticationProvider {
|
||||
for (String role : token.getAccount().getRoles()) {
|
||||
grantedAuthorities.add(new KeycloakRole(role));
|
||||
}
|
||||
return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), mapAuthorities(grantedAuthorities));
|
||||
return new KeycloakAuthenticationToken(token.getAccount(), mapAuthorities(grantedAuthorities));
|
||||
}
|
||||
|
||||
private Collection<? extends GrantedAuthority> mapAuthorities(
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ public class SpringSecurityRequestAuthenticator extends RequestAuthenticator {
|
||||
|
||||
logger.debug("Completing bearer authentication. Bearer roles: {} ",roles);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account, false));
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
|
||||
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -19,9 +19,6 @@ package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -60,8 +57,7 @@ public class SimpleHttpFacade implements OIDCHttpFacade {
|
||||
SecurityContext context = SecurityContextHolder.getContext();
|
||||
|
||||
if (context != null && context.getAuthentication() != null) {
|
||||
KeycloakAuthenticationToken authentication = (KeycloakAuthenticationToken) context.getAuthentication();
|
||||
return authentication.getAccount().getKeycloakSecurityContext();
|
||||
return (KeycloakSecurityContext) context.getAuthentication().getDetails();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
+58
-45
@@ -17,14 +17,6 @@
|
||||
|
||||
package org.keycloak.adapters.springsecurity.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
@@ -33,11 +25,10 @@ import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationEntryPoint;
|
||||
import org.keycloak.adapters.springsecurity.authentication.SpringSecurityRequestAuthenticator;
|
||||
import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.token.AdapterTokenStoreFactory;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.adapters.springsecurity.token.SpringSecurityAdapterTokenStoreFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -50,12 +41,19 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Provides a Keycloak authentication processing filter.
|
||||
*
|
||||
@@ -65,17 +63,15 @@ import org.springframework.util.Assert;
|
||||
public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter implements ApplicationContextAware {
|
||||
public static final String DEFAULT_LOGIN_URL = "/sso/login";
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
public static final String SCHEME_BEARER = "bearer ";
|
||||
public static final String SCHEME_BASIC = "basic ";
|
||||
|
||||
/**
|
||||
* Request matcher that matches requests to the {@link KeycloakAuthenticationEntryPoint#DEFAULT_LOGIN_URI default login URI}
|
||||
* and any request with a <code>Authorization</code> header.
|
||||
*/
|
||||
public static final RequestMatcher DEFAULT_REQUEST_MATCHER =
|
||||
new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(DEFAULT_LOGIN_URL),
|
||||
new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER),
|
||||
new QueryParamPresenceRequestMatcher(OAuth2Constants.ACCESS_TOKEN)
|
||||
);
|
||||
new OrRequestMatcher(new AntPathRequestMatcher(DEFAULT_LOGIN_URL), new RequestHeaderRequestMatcher(AUTHORIZATION_HEADER));
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(KeycloakAuthenticationProcessingFilter.class);
|
||||
|
||||
@@ -93,7 +89,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
|
||||
this(authenticationManager, DEFAULT_REQUEST_MATCHER);
|
||||
setAuthenticationFailureHandler(new KeycloakAuthenticationFailureHandler());
|
||||
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(DEFAULT_LOGIN_URL));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +109,7 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
*
|
||||
*/
|
||||
public KeycloakAuthenticationProcessingFilter(AuthenticationManager authenticationManager, RequestMatcher
|
||||
requiresAuthenticationRequestMatcher) {
|
||||
requiresAuthenticationRequestMatcher) {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||
this.authenticationManager = authenticationManager;
|
||||
@@ -136,10 +132,6 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
|
||||
HttpFacade facade = new SimpleHttpFacade(request, response);
|
||||
KeycloakDeployment deployment = adapterDeploymentContext.resolveDeployment(facade);
|
||||
|
||||
// using Spring authenticationFailureHandler
|
||||
deployment.setDelegateBearerErrorResponseSending(true);
|
||||
|
||||
AdapterTokenStore tokenStore = adapterTokenStoreFactory.createAdapterTokenStore(deployment, request);
|
||||
RequestAuthenticator authenticator
|
||||
= new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, -1);
|
||||
@@ -148,27 +140,9 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
log.debug("Auth outcome: {}", result);
|
||||
|
||||
if (AuthOutcome.FAILED.equals(result)) {
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
throw new KeycloakAuthenticationException("Invalid authorization header, see WWW-Authenticate header for details");
|
||||
throw new KeycloakAuthenticationException("Auth outcome: " + result);
|
||||
}
|
||||
|
||||
if (AuthOutcome.NOT_ATTEMPTED.equals(result)) {
|
||||
AuthChallenge challenge = authenticator.getChallenge();
|
||||
if (challenge != null) {
|
||||
challenge.challenge(facade);
|
||||
}
|
||||
if (deployment.isBearerOnly()) {
|
||||
// no redirection in this mode, throwing exception for the spring handler
|
||||
throw new KeycloakAuthenticationException("Authorization header not found, see WWW-Authenticate header");
|
||||
} else {
|
||||
// let continue if challenged, it may redirect
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
else if (AuthOutcome.AUTHENTICATED.equals(result)) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
Assert.notNull(authentication, "Authentication SecurityContextHolder was null");
|
||||
@@ -183,10 +157,36 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request was made with a bearer token authorization header.
|
||||
*
|
||||
* @param request the current <code>HttpServletRequest</code>
|
||||
*
|
||||
* @return <code>true</code> if the <code>request</code> was made with a bearer token authorization header;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
protected boolean isBearerTokenRequest(HttpServletRequest request) {
|
||||
String authValue = request.getHeader(AUTHORIZATION_HEADER);
|
||||
return authValue != null && authValue.toLowerCase().startsWith(SCHEME_BEARER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the request was made with a Basic authentication authorization header.
|
||||
*
|
||||
* @param request the current <code>HttpServletRequest</code>
|
||||
* @return <code>true</code> if the <code>request</code> was made with a Basic authentication authorization header;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
protected boolean isBasicAuthRequest(HttpServletRequest request) {
|
||||
String authValue = request.getHeader(AUTHORIZATION_HEADER);
|
||||
return authValue != null && authValue.toLowerCase().startsWith(SCHEME_BASIC);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
if (authResult instanceof KeycloakAuthenticationToken && ((KeycloakAuthenticationToken) authResult).isInteractive()) {
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
|
||||
if (!(this.isBearerTokenRequest(request) || this.isBasicAuthRequest(request))) {
|
||||
super.successfulAuthentication(request, response, chain, authResult);
|
||||
return;
|
||||
}
|
||||
@@ -207,11 +207,24 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
} finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
AuthenticationException failed) throws IOException, ServletException {
|
||||
|
||||
if (this.isBearerTokenRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate bearer token");
|
||||
return;
|
||||
}
|
||||
else if (this.isBasicAuthRequest(request)) {
|
||||
SecurityContextHolder.clearContext();
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
|
||||
return;
|
||||
}
|
||||
|
||||
super.unsuccessfulAuthentication(request, response, failed);
|
||||
}
|
||||
|
||||
@@ -249,4 +262,4 @@ public class KeycloakAuthenticationProcessingFilter extends AbstractAuthenticati
|
||||
public final void setContinueChainBeforeSuccessfulAuthentication(boolean continueChainBeforeSuccessfulAuthentication) {
|
||||
throw new UnsupportedOperationException("This filter does not support explicitly setting a continue chain before success policy");
|
||||
}
|
||||
}
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.springsecurity.filter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
/**
|
||||
* Spring RequestMatcher that checks for the presence of a query parameter.
|
||||
*
|
||||
* @author <a href="mailto:glavoie@gmail.com">Gabriel Lavoie</a>
|
||||
*/
|
||||
public class QueryParamPresenceRequestMatcher implements RequestMatcher {
|
||||
private String param;
|
||||
|
||||
public QueryParamPresenceRequestMatcher(String param) {
|
||||
this.param = param;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(HttpServletRequest httpServletRequest) {
|
||||
return param != null && httpServletRequest.getParameter(param) != null;
|
||||
}
|
||||
}
|
||||
+2
-9
@@ -38,27 +38,24 @@ import java.util.Collection;
|
||||
public class KeycloakAuthenticationToken extends AbstractAuthenticationToken implements Authentication {
|
||||
|
||||
private Principal principal;
|
||||
private boolean interactive;
|
||||
|
||||
/**
|
||||
* Creates a new, unauthenticated Keycloak security token for the given account.
|
||||
*/
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account, boolean interactive) {
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account) {
|
||||
super(null);
|
||||
Assert.notNull(account, "KeycloakAccount cannot be null");
|
||||
Assert.notNull(account.getPrincipal(), "KeycloakAccount.getPrincipal() cannot be null");
|
||||
this.principal = account.getPrincipal();
|
||||
this.setDetails(account);
|
||||
this.interactive = interactive;
|
||||
}
|
||||
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account, boolean interactive, Collection<? extends GrantedAuthority> authorities) {
|
||||
public KeycloakAuthenticationToken(KeycloakAccount account, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
Assert.notNull(account, "KeycloakAccount cannot be null");
|
||||
Assert.notNull(account.getPrincipal(), "KeycloakAccount.getPrincipal() cannot be null");
|
||||
this.principal = account.getPrincipal();
|
||||
this.setDetails(account);
|
||||
this.interactive = interactive;
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@@ -75,8 +72,4 @@ public class KeycloakAuthenticationToken extends AbstractAuthenticationToken imp
|
||||
public OidcKeycloakAccount getAccount() {
|
||||
return (OidcKeycloakAccount) this.getDetails();
|
||||
}
|
||||
|
||||
public boolean isInteractive() {
|
||||
return interactive;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -105,7 +105,7 @@ public class SpringSecurityTokenStore implements AdapterTokenStore {
|
||||
}
|
||||
|
||||
logger.debug("Saving account info {}", account);
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account, true));
|
||||
SecurityContextHolder.getContext().setAuthentication(new KeycloakAuthenticationToken(account));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+8
-18
@@ -44,7 +44,6 @@ import static org.mockito.Mockito.mock;
|
||||
public class KeycloakAuthenticationProviderTest {
|
||||
private KeycloakAuthenticationProvider provider = new KeycloakAuthenticationProvider();
|
||||
private KeycloakAuthenticationToken token;
|
||||
private KeycloakAuthenticationToken interactiveToken;
|
||||
private Set<String> roles = Sets.newSet("user", "admin");
|
||||
|
||||
@Before
|
||||
@@ -53,18 +52,18 @@ public class KeycloakAuthenticationProviderTest {
|
||||
RefreshableKeycloakSecurityContext securityContext = mock(RefreshableKeycloakSecurityContext.class);
|
||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, securityContext);
|
||||
|
||||
token = new KeycloakAuthenticationToken(account, false);
|
||||
interactiveToken = new KeycloakAuthenticationToken(account, true);
|
||||
token = new KeycloakAuthenticationToken(account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticate() throws Exception {
|
||||
assertAuthenticationResult(provider.authenticate(token));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateInteractive() throws Exception {
|
||||
assertAuthenticationResult(provider.authenticate(interactiveToken));
|
||||
Authentication result = provider.authenticate(token);
|
||||
assertNotNull(result);
|
||||
assertEquals(roles, AuthorityUtils.authorityListToSet(result.getAuthorities()));
|
||||
assertTrue(result.isAuthenticated());
|
||||
assertNotNull(result.getPrincipal());
|
||||
assertNotNull(result.getCredentials());
|
||||
assertNotNull(result.getDetails());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -84,13 +83,4 @@ public class KeycloakAuthenticationProviderTest {
|
||||
assertEquals(Sets.newSet("ROLE_USER", "ROLE_ADMIN"),
|
||||
AuthorityUtils.authorityListToSet(result.getAuthorities()));
|
||||
}
|
||||
|
||||
private void assertAuthenticationResult(Authentication result) {
|
||||
assertNotNull(result);
|
||||
assertEquals(roles, AuthorityUtils.authorityListToSet(result.getAuthorities()));
|
||||
assertTrue(result.isAuthenticated());
|
||||
assertNotNull(result.getPrincipal());
|
||||
assertNotNull(result.getCredentials());
|
||||
assertNotNull(result.getDetails());
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package org.keycloak.adapters.springsecurity.facade;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.spi.KeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.mockito.internal.util.collections.Sets;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class SimpleHttpFacadeTest {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
SecurityContext springSecurityContext = SecurityContextHolder.createEmptyContext();
|
||||
SecurityContextHolder.setContext(springSecurityContext);
|
||||
Set<String> roles = Sets.newSet("user");
|
||||
Principal principal = mock(Principal.class);
|
||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = mock(RefreshableKeycloakSecurityContext.class);
|
||||
KeycloakAccount account = new SimpleKeycloakAccount(principal, roles, keycloakSecurityContext);
|
||||
KeycloakAuthenticationToken token = new KeycloakAuthenticationToken(account, false);
|
||||
springSecurityContext.setAuthentication(token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRetrieveKeycloakSecurityContext() {
|
||||
SimpleHttpFacade facade = new SimpleHttpFacade(new MockHttpServletRequest(), new MockHttpServletResponse());
|
||||
|
||||
assertNotNull(facade.getSecurityContext());
|
||||
}
|
||||
}
|
||||
+37
-26
@@ -27,7 +27,6 @@ import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.springsecurity.KeycloakAuthenticationException;
|
||||
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
|
||||
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
@@ -53,6 +52,7 @@ import java.util.UUID;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -89,8 +89,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
|
||||
@Mock
|
||||
private AuthenticationFailureHandler failureHandler;
|
||||
|
||||
private KeycloakAuthenticationFailureHandler keycloakFailureHandler;
|
||||
|
||||
@Mock
|
||||
private OidcKeycloakAccount keycloakAccount;
|
||||
@@ -108,7 +106,6 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
request = spy(new MockHttpServletRequest());
|
||||
filter = new KeycloakAuthenticationProcessingFilter(authenticationManager);
|
||||
keycloakFailureHandler = new KeycloakAuthenticationFailureHandler();
|
||||
|
||||
filter.setApplicationContext(applicationContext);
|
||||
filter.setAuthenticationSuccessHandler(successHandler);
|
||||
@@ -123,6 +120,34 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
filter.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBearerTokenRequest() throws Exception {
|
||||
assertFalse(filter.isBearerTokenRequest(request));
|
||||
this.setBearerAuthHeader(request);
|
||||
assertTrue(filter.isBearerTokenRequest(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBearerTokenRequestCaseInsensitive() throws Exception {
|
||||
assertFalse(filter.isBearerTokenRequest(request));
|
||||
this.setAuthorizationHeader(request, "bearer");
|
||||
assertTrue(filter.isBearerTokenRequest(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBasicAuthRequest() throws Exception {
|
||||
assertFalse(filter.isBasicAuthRequest(request));
|
||||
this.setBasicAuthHeader(request);
|
||||
assertTrue(filter.isBasicAuthRequest(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsBasicAuthRequestCaseInsensitive() throws Exception {
|
||||
assertFalse(filter.isBasicAuthRequest(request));
|
||||
this.setAuthorizationHeader(request, "basic");
|
||||
assertTrue(filter.isBasicAuthRequest(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAttemptAuthenticationExpectRedirect() throws Exception {
|
||||
when(keycloakDeployment.getAuthUrl()).thenReturn(KeycloakUriBuilder.fromUri("http://localhost:8080/auth"));
|
||||
@@ -130,8 +155,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
when(keycloakDeployment.getStateCookieName()).thenReturn("kc-cookie");
|
||||
when(keycloakDeployment.getSslRequired()).thenReturn(SslRequired.NONE);
|
||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
|
||||
|
||||
filter.attemptAuthentication(request, response);
|
||||
|
||||
verify(response).setStatus(302);
|
||||
verify(response).setHeader(eq("Location"), startsWith("http://localhost:8080/auth"));
|
||||
}
|
||||
@@ -142,16 +167,9 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
filter.attemptAuthentication(request, response);
|
||||
}
|
||||
|
||||
@Test(expected = KeycloakAuthenticationException.class)
|
||||
public void testAttemptAuthenticationWithInvalidTokenBearerOnly() throws Exception {
|
||||
when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.TRUE);
|
||||
request.addHeader("Authorization", "Bearer xxx");
|
||||
filter.attemptAuthentication(request, response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAuthenticationInteractive() throws Exception {
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, true, authorities);
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
|
||||
filter.successfulAuthentication(request, response, chain, authentication);
|
||||
|
||||
verify(successHandler).onAuthenticationSuccess(eq(request), eq(response), eq(authentication));
|
||||
@@ -160,7 +178,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAuthenticationBearer() throws Exception {
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, false, authorities);
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
|
||||
this.setBearerAuthHeader(request);
|
||||
filter.successfulAuthentication(request, response, chain, authentication);
|
||||
|
||||
@@ -171,7 +189,7 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulAuthenticationBasicAuth() throws Exception {
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, false, authorities);
|
||||
Authentication authentication = new KeycloakAuthenticationToken(keycloakAccount, authorities);
|
||||
this.setBasicAuthHeader(request);
|
||||
filter.successfulAuthentication(request, response, chain, authentication);
|
||||
|
||||
@@ -192,7 +210,8 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBearerAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@@ -201,18 +220,10 @@ public class KeycloakAuthenticationProcessingFilterTest {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
this.setBasicAuthHeader(request);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
verify(failureHandler).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), anyString());
|
||||
verify(failureHandler, never()).onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class),
|
||||
any(AuthenticationException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultFailureHanlder() throws Exception {
|
||||
AuthenticationException exception = new BadCredentialsException("OOPS");
|
||||
filter.setAuthenticationFailureHandler(keycloakFailureHandler);
|
||||
filter.unsuccessfulAuthentication(request, response, exception);
|
||||
|
||||
verify(response).sendError(eq(HttpServletResponse.SC_UNAUTHORIZED), any(String.class));
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testSetAllowSessionCreation() throws Exception {
|
||||
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.springsecurity.filter;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
|
||||
public class QueryParamPresenceRequestMatcherTest {
|
||||
private static final String ROOT_CONTEXT_PATH = "";
|
||||
|
||||
private static final String VALID_PARAMETER = "access_token";
|
||||
|
||||
private QueryParamPresenceRequestMatcher matcher = new QueryParamPresenceRequestMatcher(VALID_PARAMETER);
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
request = new MockHttpServletRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotMatchWithoutQueryParameter() throws Exception {
|
||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.EMPTY_MAP);
|
||||
assertFalse(matcher.matches(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchesWithValidParameter() throws Exception {
|
||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.singletonMap(VALID_PARAMETER, (Object) "123"));
|
||||
assertTrue(matcher.matches(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoesNotMatchWithInvalidParameter() throws Exception {
|
||||
prepareRequest(HttpMethod.GET, ROOT_CONTEXT_PATH, "some/random/uri", Collections.singletonMap("some_parameter", (Object) "123"));
|
||||
assertFalse(matcher.matches(request));
|
||||
}
|
||||
|
||||
private void prepareRequest(HttpMethod method, String contextPath, String uri, Map<String, Object> params) {
|
||||
request.setMethod(method.name());
|
||||
request.setContextPath(contextPath);
|
||||
request.setRequestURI(contextPath + "/" + uri);
|
||||
request.setParameters(params);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak Tomcat Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+6
-3
@@ -17,7 +17,11 @@
|
||||
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.*;
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Lifecycle;
|
||||
import org.apache.catalina.LifecycleEvent;
|
||||
import org.apache.catalina.LifecycleListener;
|
||||
import org.apache.catalina.Manager;
|
||||
import org.apache.catalina.authenticator.FormAuthenticator;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.connector.Response;
|
||||
@@ -132,7 +136,7 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
||||
}
|
||||
|
||||
context.getServletContext().setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
AbstractAuthenticatedActionsValve actions = createAuthenticatedActionsValve(deploymentContext, getNext(), getContainer());
|
||||
AuthenticatedActionsValve actions = new AuthenticatedActionsValve(deploymentContext, getNext(), getContainer());
|
||||
setNext(actions);
|
||||
|
||||
nodesRegistrationManagement = new NodesRegistrationManagement();
|
||||
@@ -185,7 +189,6 @@ public abstract class AbstractKeycloakAuthenticatorValve extends FormAuthenticat
|
||||
|
||||
protected abstract GenericPrincipalFactory createPrincipalFactory();
|
||||
protected abstract boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException;
|
||||
protected abstract AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container);
|
||||
|
||||
protected boolean authenticateInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
|
||||
CatalinaHttpFacade facade = new OIDCCatalinaHttpFacade(request, response);
|
||||
|
||||
+5
-4
@@ -31,7 +31,7 @@ import javax.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Abstract base for pre-installed actions that must be authenticated
|
||||
* Pre-installed actions that must be authenticated
|
||||
* <p/>
|
||||
* Actions include:
|
||||
* <p/>
|
||||
@@ -41,17 +41,18 @@ import java.io.IOException;
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractAuthenticatedActionsValve extends ValveBase {
|
||||
private static final Logger log = Logger.getLogger(AbstractAuthenticatedActionsValve.class);
|
||||
public class AuthenticatedActionsValve extends ValveBase {
|
||||
private static final Logger log = Logger.getLogger(AuthenticatedActionsValve.class);
|
||||
protected AdapterDeploymentContext deploymentContext;
|
||||
|
||||
public AbstractAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
this.deploymentContext = deploymentContext;
|
||||
if (next == null) throw new RuntimeException("Next valve is null!!!");
|
||||
setNext(next);
|
||||
setContainer(container);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void invoke(Request request, Response response) throws IOException, ServletException {
|
||||
log.debugv("AuthenticatedActionsValve.invoke {0}", request.getRequestURI());
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
|
||||
public class AuthenticatedActionsValve extends AbstractAuthenticatedActionsValve {
|
||||
|
||||
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
super(deploymentContext, next, container);
|
||||
}
|
||||
}
|
||||
-7
@@ -17,15 +17,12 @@
|
||||
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
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.apache.catalina.realm.GenericPrincipal;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -54,10 +51,6 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
return new AuthenticatedActionsValve(deploymentContext, next, container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws LifecycleException {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
|
||||
public class AuthenticatedActionsValve extends AbstractAuthenticatedActionsValve {
|
||||
|
||||
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
super(deploymentContext, next, container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
-8
@@ -17,14 +17,11 @@
|
||||
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
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.apache.catalina.realm.GenericPrincipal;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -72,9 +69,4 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
return new AuthenticatedActionsValve(deploymentContext, next, container);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-tomcat-integration-pom</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
|
||||
public class AuthenticatedActionsValve extends AbstractAuthenticatedActionsValve {
|
||||
|
||||
public AuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
super(deploymentContext, next, container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsyncSupported() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
-8
@@ -17,14 +17,11 @@
|
||||
|
||||
package org.keycloak.adapters.tomcat;
|
||||
|
||||
import org.apache.catalina.Container;
|
||||
import org.apache.catalina.Valve;
|
||||
import org.apache.catalina.authenticator.FormAuthenticator;
|
||||
import org.apache.catalina.connector.Request;
|
||||
import org.apache.catalina.core.StandardContext;
|
||||
import org.apache.catalina.realm.GenericPrincipal;
|
||||
import org.apache.tomcat.util.descriptor.web.LoginConfig;
|
||||
import org.keycloak.adapters.AdapterDeploymentContext;
|
||||
import org.keycloak.adapters.AdapterTokenStore;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
@@ -105,9 +102,4 @@ public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorVal
|
||||
protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
|
||||
return super.getTokenStore(request, facade, resolvedDeployment);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractAuthenticatedActionsValve createAuthenticatedActionsValve(AdapterDeploymentContext deploymentContext, Valve next, Container container) {
|
||||
return new AuthenticatedActionsValve(deploymentContext, next, container);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
+1
-20
@@ -50,9 +50,7 @@ import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
@@ -68,7 +66,6 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||
private ElytronAccount account;
|
||||
private SecurityIdentity securityIdentity;
|
||||
private boolean restored;
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
|
||||
public ElytronHttpFacade(HttpServerRequest request, AdapterDeploymentContext deploymentContext, CallbackHandler handler) {
|
||||
this.request = request;
|
||||
@@ -93,11 +90,6 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||
|
||||
void authenticationComplete() {
|
||||
if (securityIdentity != null) {
|
||||
HttpScope requestScope = request.getScope(Scope.EXCHANGE);
|
||||
RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
|
||||
|
||||
requestScope.setAttachment(KeycloakSecurityContext.class.getName(), keycloakSecurityContext);
|
||||
|
||||
this.request.authenticationComplete(response -> {
|
||||
if (!restored) {
|
||||
responseConsumer.accept(response);
|
||||
@@ -264,7 +256,6 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||
@Override
|
||||
public Response getResponse() {
|
||||
return new Response() {
|
||||
|
||||
@Override
|
||||
public void setStatus(final int status) {
|
||||
responseConsumer = responseConsumer.andThen(response -> response.setStatusCode(status));
|
||||
@@ -272,17 +263,7 @@ class ElytronHttpFacade implements OIDCHttpFacade {
|
||||
|
||||
@Override
|
||||
public void addHeader(final String name, final String value) {
|
||||
headers.put(name, value);
|
||||
responseConsumer = responseConsumer.andThen(new Consumer<HttpServerResponse>() {
|
||||
@Override
|
||||
public void accept(HttpServerResponse response) {
|
||||
String latestValue = headers.get(name);
|
||||
|
||||
if (latestValue.equals(value)) {
|
||||
response.addResponseHeader(name, latestValue);
|
||||
}
|
||||
}
|
||||
});
|
||||
responseConsumer = responseConsumer.andThen(response -> response.addResponseHeader(name, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
|
||||
@Override
|
||||
public void checkCurrentToken() {
|
||||
HttpScope session = httpFacade.getScope(Scope.SESSION);
|
||||
if (session == null || !session.exists()) return;
|
||||
if (!session.exists()) return;
|
||||
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) session.getAttachment(KeycloakSecurityContext.class.getName());
|
||||
if (securityContext == null) return;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ElytronSessionTokenStore implements ElytronTokeStore {
|
||||
public boolean isCached(RequestAuthenticator authenticator) {
|
||||
HttpScope session = this.httpFacade.getScope(Scope.SESSION);
|
||||
|
||||
if (session == null || !session.supportsAttachments()) {
|
||||
if (session == null) {
|
||||
log.debug("session was null, returning null");
|
||||
return false;
|
||||
}
|
||||
|
||||
+20
-25
@@ -42,40 +42,35 @@ import java.io.InputStream;
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakConfigurationServletListener implements ServletContextListener {
|
||||
|
||||
static final String ADAPTER_DEPLOYMENT_CONTEXT_ATTRIBUTE = AdapterDeploymentContext.class.getName() + ".elytron";
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
ServletContext servletContext = sce.getServletContext();
|
||||
String configResolverClass = servletContext.getInitParameter("keycloak.config.resolver");
|
||||
KeycloakConfigResolver configResolver;
|
||||
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) servletContext.getAttribute(AdapterDeploymentContext.class.getName());
|
||||
AdapterDeploymentContext deploymentContext;
|
||||
|
||||
if (deploymentContext == null) {
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
configResolver = (KeycloakConfigResolver) servletContext.getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
} catch (Exception ex) {
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
} else {
|
||||
InputStream is = getConfigInputStream(servletContext);
|
||||
|
||||
KeycloakDeployment deployment;
|
||||
|
||||
if (is == null) {
|
||||
deployment = new KeycloakDeployment();
|
||||
} else {
|
||||
deployment = KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
|
||||
deploymentContext = new AdapterDeploymentContext(deployment);
|
||||
if (configResolverClass != null) {
|
||||
try {
|
||||
configResolver = (KeycloakConfigResolver) servletContext.getClassLoader().loadClass(configResolverClass).newInstance();
|
||||
deploymentContext = new AdapterDeploymentContext(configResolver);
|
||||
} catch (Exception ex) {
|
||||
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
|
||||
}
|
||||
} else {
|
||||
InputStream is = getConfigInputStream(servletContext);
|
||||
|
||||
KeycloakDeployment deployment;
|
||||
|
||||
if (is == null) {
|
||||
deployment = new KeycloakDeployment();
|
||||
} else {
|
||||
deployment = KeycloakDeploymentBuilder.build(is);
|
||||
}
|
||||
|
||||
deploymentContext = new AdapterDeploymentContext(deployment);
|
||||
}
|
||||
|
||||
servletContext.setAttribute(ADAPTER_DEPLOYMENT_CONTEXT_ATTRIBUTE, deploymentContext);
|
||||
servletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-2
@@ -31,6 +31,7 @@ import org.keycloak.adapters.AuthenticatedActionsHandler;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.NodesRegistrationManagement;
|
||||
import org.keycloak.adapters.PreAuthActionsHandler;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
@@ -70,7 +71,7 @@ class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticat
|
||||
AdapterDeploymentContext deploymentContext = getDeploymentContext(request);
|
||||
|
||||
if (deploymentContext == null) {
|
||||
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI(), getMechanismName());
|
||||
LOGGER.debugf("Ignoring request for path [%s] from mechanism [%s]. No deployment context found.", request.getRequestURI());
|
||||
request.noAuthenticationInProgress();
|
||||
return;
|
||||
}
|
||||
@@ -126,7 +127,7 @@ class KeycloakHttpServerAuthenticationMechanism implements HttpServerAuthenticat
|
||||
|
||||
private AdapterDeploymentContext getDeploymentContext(HttpServerRequest request) {
|
||||
if (this.deploymentContext == null) {
|
||||
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(KeycloakConfigurationServletListener.ADAPTER_DEPLOYMENT_CONTEXT_ATTRIBUTE);
|
||||
return (AdapterDeploymentContext) request.getScope(Scope.APPLICATION).getAttachment(AdapterDeploymentContext.class.getName());
|
||||
}
|
||||
|
||||
return this.deploymentContext;
|
||||
|
||||
+2
-3
@@ -17,7 +17,6 @@
|
||||
package org.keycloak.adapters.elytron;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.security.spec.AlgorithmParameterSpec;
|
||||
import java.util.Set;
|
||||
|
||||
import org.keycloak.KeycloakPrincipal;
|
||||
@@ -55,7 +54,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.UNSUPPORTED;
|
||||
}
|
||||
|
||||
@@ -93,7 +92,7 @@ public class KeycloakSecurityRealm implements SecurityRealm {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName, AlgorithmParameterSpec parameterSpec) throws RealmUnavailableException {
|
||||
public SupportLevel getCredentialAcquireSupport(Class<? extends Credential> credentialType, String algorithmName) throws RealmUnavailableException {
|
||||
return SupportLevel.UNSUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<name>Keycloak WildFly Integration</name>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<parent>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<version>3.4.0.Final</version>
|
||||
<version>3.1.0.Final</version>
|
||||
<relativePath>../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
+1
-16
@@ -124,12 +124,6 @@ public class SharedAttributeDefinitons {
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition CORS_EXPOSED_HEADERS =
|
||||
new SimpleAttributeDefinitionBuilder("cors-exposed-headers", ModelType.STRING, true)
|
||||
.setXmlName("cors-exposed-headers")
|
||||
.setAllowExpression(true)
|
||||
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
|
||||
new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
|
||||
.setXmlName("expose-token")
|
||||
@@ -178,14 +172,7 @@ public class SharedAttributeDefinitons {
|
||||
.setXmlName("autodetect-bearer-only")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
|
||||
protected static final SimpleAttributeDefinition IGNORE_OAUTH_QUERY_PARAMETER =
|
||||
new SimpleAttributeDefinitionBuilder("ignore-oauth-query-parameter", ModelType.BOOLEAN, true)
|
||||
.setXmlName("ignore-oauth-query-parameter")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
static {
|
||||
@@ -204,7 +191,6 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(CORS_MAX_AGE);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
|
||||
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
|
||||
ATTRIBUTES.add(CORS_EXPOSED_HEADERS);
|
||||
ATTRIBUTES.add(EXPOSE_TOKEN);
|
||||
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
|
||||
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
|
||||
@@ -213,7 +199,6 @@ public class SharedAttributeDefinitons {
|
||||
ATTRIBUTES.add(TOKEN_STORE);
|
||||
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
|
||||
ATTRIBUTES.add(AUTODETECT_BEARER_ONLY);
|
||||
ATTRIBUTES.add(IGNORE_OAUTH_QUERY_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
-4
@@ -39,7 +39,6 @@ keycloak.realm.client-key-password=n/a
|
||||
keycloak.realm.cors-max-age=CORS max-age header
|
||||
keycloak.realm.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.realm.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.realm.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.realm.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.realm.always-refresh-token=Refresh token on every single web request
|
||||
@@ -48,7 +47,6 @@ keycloak.realm.register-node-period=how often to re-register node
|
||||
keycloak.realm.token-store=cookie or session storage for auth session data
|
||||
keycloak.realm.principal-attribute=token attribute to use to set Principal name
|
||||
keycloak.realm.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.realm.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment=A deployment secured by Keycloak
|
||||
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
|
||||
@@ -75,7 +73,6 @@ keycloak.secure-deployment.client-key-password=n/a
|
||||
keycloak.secure-deployment.cors-max-age=CORS max-age header
|
||||
keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
|
||||
keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
|
||||
keycloak.secure-deployment.cors-exposed-headers=CORS exposed headers
|
||||
keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
|
||||
keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
|
||||
keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
|
||||
@@ -87,7 +84,6 @@ keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is
|
||||
keycloak.secure-deployment.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
keycloak.secure-deployment.autodetect-bearer-only=autodetect bearer-only requests
|
||||
keycloak.secure-deployment.ignore-oauth-query-parameter=disable query parameter parsing for access_token
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
|
||||
Loaded 100 of 3093 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user