Compare commits

..
6286 changed files with 189953 additions and 852030 deletions

No files matched your search

-20
View File
@@ -1,20 +0,0 @@
* text=auto
*.html text eol=lf
*.java text eol=lf
*.js text eol=lf
*.json text eol=lf
*.jsp text eol=lf
*.md text eol=lf
*.properties text eol=lf
*.svg text auto
*.xml text eol=lf
*.xsl text eol=lf
*.png binary
*.jpg binary
*.gif binary
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
+1 -2
View File
@@ -9,11 +9,10 @@
.settings
.classpath
# NetBeans #
############
nbactions.xml
nb-configuration.xml
catalog.xml
# Compiled source #
###################
-20
View File
@@ -1,20 +0,0 @@
language: java
env:
global:
- MAVEN_SKIP_RC=true
- MAVEN_OPTS="-Xms512m -Xmx2048m"
jdk:
- oraclejdk8
before_script:
- export MAVEN_SKIP_RC=true
install:
- mvn install -Pdistribution -DskipTests=true -B -V -q
script:
- mvn test -B
sudo: false
+31 -69
View File
@@ -1,74 +1,36 @@
Keycloak
keycloak
========
Open Source Identity and Access Management for modern Applications and Services.
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
and video tutorials.
For more information about Keycloak visit [Keycloak homepage](http://keycloak.org) and [Keycloak blog](http://blog.keycloak.org).
Keycloak is an SSO Service for web apps and REST services.
It can be used for social applications as well as enterprise applications. It is based on OpenID Connect with support for SAML 2.0 as well.
Here's some of the features:
* SSO and Single Log Out for browser applications
* Social Broker. Enable Google, Facebook, Yahoo, Twitter social login with no code required.
* Optional LDAP/Active Directory integration
* Optional User Registration
* Password and TOTP support (via Google Authenticator). Client cert auth coming soon.
* User session management from both admin and user perspective
* Customizable themes for user facing pages: login, grant pages, account management, emails, and admin console all customizable!
* OAuth Bearer token auth for REST Services
* Integrated Browser App to REST Service token propagation
* Admin REST API
* OAuth 2.0 Grant requests
* CORS Support
* CORS Web Origin management and validation
* Completely centrally managed user and role mapping metadata. Minimal configuration at the application side
* Admin Console for managing users, roles, role mappings, applications, user sessions, allowed CORS web origins, and OAuth clients.
* Deployable as a WAR, appliance, or an Openshift cloud service (SaaS).
* Supports JBoss AS7, EAP 6.x, and Wildfly applications. Plans to support Node.js, RAILS, GRAILS, and other non-Java applications.
* Javascript/HTML 5 adapter for pure Javascript apps
* Session management from admin console
* Revocation policies
* Password policies
* OpenID Connect Support
* SAML Support
Building
--------
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
java -version
mvn -version
git --version
First clone the Keycloak repository:
git clone https://github.com/keycloak/keycloak.git
cd keycloak
To build Keycloak run:
mvn install
This will build all modules and run the testsuite.
To build the distribution run:
mvn install -Pdistribution
Once completed you will find distribution archives in `distribution`.
Starting Keycloak
-----------------
To start Keycloak during development first build as specficied above, then run:
mvn -f testsuite/integration/pom.xml exec:java -Pkeycloak-server
To start Keycloak from the appliance distribution first build the distribution it as specified above, then run:
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`.
Help and Documentation
----------------------
* [Documentation](http://keycloak.jboss.org/docs) - User Guide, Admin REST API and Javadocs
* [User Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-user) - Mailing list to ask for help and general questions about Keycloak
* [JIRA](https://issues.jboss.org/projects/KEYCLOAK) - Issue tracker for bugs and feature requests
Contributing
------------
* Developer documentation
* [Hacking on Keycloak](misc/HackingOnKeycloak.md) - How to become a Keycloak contributor
* [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server)
* [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases
* [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
License
-------
* [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
Please visit [http://keycloak.org](http://keycloak.org) for more information on Keycloak including how to download, documentation,
and video tutorials.
-140
View File
@@ -1,140 +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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-adapter-core</artifactId>
<name>Keycloak Adapter Core</name>
<description/>
<properties>
<keycloak.osgi.export>
org.keycloak.adapters.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.keycloak.*;version="${project.version}",
org.apache.http.*;version=${apache.httpcomponents.version},
org.apache.karaf.jaas.boot.principal;resolution:=optional,
org.apache.karaf.jaas.modules;resolution:=optional,
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,61 +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.spi.AdapterSessionStore;
/**
* Abstraction for storing token info on adapter side. Intended to be per-request object
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface AdapterTokenStore extends AdapterSessionStore {
/**
* Impl can validate if current token exists and perform refreshing if it exists and is expired
*/
void checkCurrentToken();
/**
* Check if we are logged already (we have already valid and successfully refreshed accessToken). Establish security context if yes
*
* @param authenticator used for actual request authentication
* @return true if we are logged-in already
*/
boolean isCached(RequestAuthenticator authenticator);
/**
* Finish successful OAuth2 login and store validated account
*
* @param account
*/
void saveAccountInfo(OidcKeycloakAccount account);
/**
* Handle logout on store side and possibly propagate logout call to Keycloak
*/
void logout();
/**
* Callback invoked after successful token refresh
*
* @param securityContext context where refresh was performed
*/
void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
}
@@ -1,94 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.common.util.UriUtils;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class AdapterUtils {
private static Logger log = Logger.getLogger(AdapterUtils.class);
public static String generateId() {
return UUID.randomUUID().toString();
}
public static Set<String> getRolesFromSecurityContext(RefreshableKeycloakSecurityContext session) {
Set<String> roles = null;
AccessToken accessToken = session.getToken();
if (session.getDeployment().isUseResourceRoleMappings()) {
if (log.isTraceEnabled()) {
log.trace("useResourceRoleMappings");
}
AccessToken.Access access = accessToken.getResourceAccess(session.getDeployment().getResourceName());
if (access != null) roles = access.getRoles();
} else {
if (log.isTraceEnabled()) {
log.trace("use realm role mappings");
}
AccessToken.Access access = accessToken.getRealmAccess();
if (access != null) roles = access.getRoles();
}
if (roles == null) roles = Collections.emptySet();
if (log.isTraceEnabled()) {
log.trace("Setting roles: ");
for (String role : roles) {
log.trace(" role: " + role);
}
}
return roles;
}
public static String getPrincipalName(KeycloakDeployment deployment, AccessToken token) {
String attr = "sub";
if (deployment.getPrincipalAttribute() != null) attr = deployment.getPrincipalAttribute();
String name = null;
if ("sub".equals(attr)) {
name = token.getSubject();
} else if ("email".equals(attr)) {
name = token.getEmail();
} else if ("preferred_username".equals(attr)) {
name = token.getPreferredUsername();
} else if ("name".equals(attr)) {
name = token.getName();
} else if ("given_name".equals(attr)) {
name = token.getGivenName();
} else if ("family_name".equals(attr)) {
name = token.getFamilyName();
} else if ("nickname".equals(attr)) {
name = token.getNickName();
}
if (name == null) name = token.getSubject();
return name;
}
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> createPrincipal(KeycloakDeployment deployment, RefreshableKeycloakSecurityContext securityContext) {
return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(getPrincipalName(deployment, securityContext.getToken()), securityContext);
}
}
@@ -1,126 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.common.util.Base64;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.util.List;
/**
* Basic auth request authenticator.
*/
public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticator {
protected Logger log = Logger.getLogger(BasicAuthRequestAuthenticator.class);
public BasicAuthRequestAuthenticator(KeycloakDeployment deployment) {
super(deployment);
}
public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_AUTHORIZATION_HEADER, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
for (String authHeader : authHeaders) {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) continue;
if (!split[0].equalsIgnoreCase("Basic")) continue;
tokenString = split[1];
}
if (tokenString == null) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
AccessTokenResponse atr=null;
try {
String userpw=new String(Base64.decode(tokenString));
String[] parts=userpw.split(":");
atr = getToken(parts[0], parts[1]);
tokenString = atr.getToken();
} catch (Exception e) {
log.debug("Failed to obtain token", e);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "no_token", e.getMessage());
return AuthOutcome.FAILED;
}
return authenticateToken(exchange, atr.getToken());
}
private AccessTokenResponse getToken(String username, String password) throws Exception {
AccessTokenResponse tokenResponse=null;
HttpClient client = deployment.getClient();
HttpPost post = new HttpPost(
KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl())
.path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()));
java.util.List <NameValuePair> formparams = new java.util.ArrayList <NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
EntityUtils.consumeQuietly(entity);
throw new java.io.IOException("Bad status: " + status);
}
if (entity == null) {
throw new java.io.IOException("No Entity");
}
java.io.InputStream is = entity.getContent();
try {
tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
} finally {
try {
is.close();
} catch (java.io.IOException ignored) { }
}
return (tokenResponse);
}
}
@@ -1,172 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.RSATokenVerifier;
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.representations.AccessToken;
import javax.security.cert.X509Certificate;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class BearerTokenRequestAuthenticator {
protected Logger log = Logger.getLogger(BearerTokenRequestAuthenticator.class);
protected String tokenString;
protected AccessToken token;
protected String surrogate;
protected AuthChallenge challenge;
protected KeycloakDeployment deployment;
public BearerTokenRequestAuthenticator(KeycloakDeployment deployment) {
this.deployment = deployment;
}
public AuthChallenge getChallenge() {
return challenge;
}
public String getTokenString() {
return tokenString;
}
public AccessToken getToken() {
return token;
}
public String getSurrogate() {
return surrogate;
}
public AuthOutcome authenticate(HttpFacade exchange) {
List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
if (authHeaders == null || authHeaders.size() == 0) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
tokenString = null;
for (String authHeader : authHeaders) {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) continue;
if (!split[0].equalsIgnoreCase("Bearer")) continue;
tokenString = split[1];
}
if (tokenString == null) {
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
return AuthOutcome.NOT_ATTEMPTED;
}
return (authenticateToken(exchange, tokenString));
}
protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
} catch (VerificationException e) {
log.error("Failed to verify token", e);
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
return AuthOutcome.FAILED;
}
if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token");
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
return AuthOutcome.FAILED;
}
boolean verifyCaller = false;
if (deployment.isUseResourceRoleMappings()) {
verifyCaller = token.isVerifyCaller(deployment.getResourceName());
} else {
verifyCaller = token.isVerifyCaller();
}
surrogate = null;
if (verifyCaller) {
if (token.getTrustedCertificates() == null || token.getTrustedCertificates().size() == 0) {
log.warn("No trusted certificates in token");
challenge = clientCertChallenge();
return AuthOutcome.FAILED;
}
// for now, we just make sure Undertow did two-way SSL
// assume JBoss Web verifies the client cert
X509Certificate[] chain = new X509Certificate[0];
try {
chain = exchange.getCertificateChain();
} catch (Exception ignore) {
}
if (chain == null || chain.length == 0) {
log.warn("No certificates provided by undertow to verify the caller");
challenge = clientCertChallenge();
return AuthOutcome.FAILED;
}
surrogate = chain[0].getSubjectDN().getName();
}
return AuthOutcome.AUTHENTICATED;
}
protected AuthChallenge clientCertChallenge() {
return new AuthChallenge() {
@Override
public int getResponseCode() {
return 0;
}
@Override
public boolean challenge(HttpFacade exchange) {
// do the same thing as client cert auth
return false;
}
};
}
protected AuthChallenge challengeResponse(HttpFacade facade, final OIDCAuthenticationError.Reason reason, final String error, final String description) {
StringBuilder header = new StringBuilder("Bearer realm=\"");
header.append(deployment.getRealm()).append("\"");
if (error != null) {
header.append(", error=\"").append(error).append("\"");
}
if (description != null) {
header.append(", error_description=\"").append(description).append("\"");
}
final String challenge = header.toString();
return new AuthChallenge() {
@Override
public int getResponseCode() {
return 401;
}
@Override
public boolean challenge(HttpFacade facade) {
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
facade.getRequest().setError(error);
facade.getResponse().addHeader("WWW-Authenticate", challenge);
facade.getResponse().sendError(401);
return true;
}
};
}
}
@@ -1,33 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface CorsHeaders {
String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
String ORIGIN = "Origin";
String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
}
@@ -1,369 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.EnvUtil;
import org.keycloak.common.util.KeystoreUtil;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Abstraction for creating HttpClients. Allows SSL configuration.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HttpClientBuilder {
public static enum HostnameVerificationPolicy {
/**
* Hostname verification is not done on the server's certificate
*/
ANY,
/**
* Allows wildcards in subdomain names i.e. *.foo.com
*/
WILDCARD,
/**
* CN must match hostname connecting to
*/
STRICT
}
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
private static class PassthroughTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
protected KeyStore truststore;
protected KeyStore clientKeyStore;
protected String clientPrivateKeyPassword;
protected boolean disableTrustManager;
protected boolean disableCookieCache = true;
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
protected SSLContext sslContext;
protected int connectionPoolSize = 100;
protected int maxPooledPerRoute = 0;
protected long connectionTTL = -1;
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
protected HostnameVerifier verifier = null;
protected long socketTimeout = -1;
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
protected long establishConnectionTimeout = -1;
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
/**
* Socket inactivity timeout
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
{
this.socketTimeout = timeout;
this.socketTimeoutUnits = unit;
return this;
}
/**
* When trying to make an initial socket connection, what is the timeout?
*
* @param timeout
* @param unit
* @return
*/
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
{
this.establishConnectionTimeout = timeout;
this.establishConnectionTimeoutUnits = unit;
return this;
}
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
this.connectionTTL = ttl;
this.connectionTTLUnit = unit;
return this;
}
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
this.maxPooledPerRoute = maxPooledPerRoute;
return this;
}
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize;
return this;
}
/**
* Disable trust management and hostname verification. <i>NOTE</i> this is a security
* hole, so only set this option if you cannot or do not want to verify the identity of the
* host you are communicating with.
*/
public HttpClientBuilder disableTrustManager() {
this.disableTrustManager = true;
return this;
}
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
return this;
}
/**
* SSL policy used to verify hostnames
*
* @param policy
* @return
*/
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
this.policy = policy;
return this;
}
public HttpClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
public HttpClientBuilder trustStore(KeyStore truststore) {
this.truststore = truststore;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = password;
return this;
}
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = new String(password);
return this;
}
static class VerifierWrapper implements X509HostnameVerifier {
protected HostnameVerifier verifier;
VerifierWrapper(HostnameVerifier verifier) {
this.verifier = verifier;
}
@Override
public void verify(String host, SSLSocket ssl) throws IOException {
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
}
@Override
public void verify(String host, X509Certificate cert) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
throw new SSLException("This verification path not implemented");
}
@Override
public boolean verify(String s, SSLSession sslSession) {
return verifier.verify(s, sslSession);
}
}
public HttpClient build() {
X509HostnameVerifier verifier = null;
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
else {
switch (policy) {
case ANY:
verifier = new AllowAllHostnameVerifier();
break;
case WILDCARD:
verifier = new BrowserCompatHostnameVerifier();
break;
case STRICT:
verifier = new StrictHostnameVerifier();
break;
}
}
try {
SSLSocketFactory sslsf = null;
SSLContext theContext = sslContext;
if (disableTrustManager) {
theContext = SSLContext.getInstance("SSL");
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
new SecureRandom());
verifier = new AllowAllHostnameVerifier();
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (theContext != null) {
sslsf = new SniSSLSocketFactory(theContext, verifier);
} else if (clientKeyStore != null || truststore != null) {
sslsf = new SniSSLSocketFactory(SSLSocketFactory.TLS, clientKeyStore, clientPrivateKeyPassword, truststore, null, verifier);
} else {
final SSLContext tlsContext = SSLContext.getInstance(SSLSocketFactory.TLS);
tlsContext.init(null, null, null);
sslsf = new SniSSLSocketFactory(tlsContext, verifier);
}
SchemeRegistry registry = new SchemeRegistry();
registry.register(
new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
Scheme httpsScheme = new Scheme("https", 443, sslsf);
registry.register(httpsScheme);
ClientConnectionManager cm = null;
if (connectionPoolSize > 0) {
ThreadSafeClientConnManager tcm = new ThreadSafeClientConnManager(registry, connectionTTL, connectionTTLUnit);
tcm.setMaxTotal(connectionPoolSize);
if (maxPooledPerRoute == 0) maxPooledPerRoute = connectionPoolSize;
tcm.setDefaultMaxPerRoute(maxPooledPerRoute);
cm = tcm;
} else {
cm = new SingleClientConnManager(registry);
}
BasicHttpParams params = new BasicHttpParams();
if (socketTimeout > -1)
{
HttpConnectionParams.setSoTimeout(params, (int) socketTimeoutUnits.toMillis(socketTimeout));
}
if (establishConnectionTimeout > -1)
{
HttpConnectionParams.setConnectionTimeout(params, (int)establishConnectionTimeoutUnits.toMillis(establishConnectionTimeout));
}
DefaultHttpClient client = new DefaultHttpClient(cm, params);
if (disableCookieCache) {
client.setCookieStore(new CookieStore() {
@Override
public void addCookie(Cookie cookie) {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public List<Cookie> getCookies() {
return Collections.emptyList();
}
@Override
public boolean clearExpired(Date date) {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void clear() {
//To change body of implemented methods use File | Settings | File Templates.
}
});
}
return client;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public HttpClient build(AdapterConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
truststorePath = EnvUtil.replace(truststorePath);
String truststorePassword = adapterConfig.getTruststorePassword();
try {
this.truststore = KeystoreUtil.loadKeyStore(truststorePath, truststorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore", e);
}
}
String clientKeystore = adapterConfig.getClientKeystore();
if (clientKeystore != null) {
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = adapterConfig.getClientKeystorePassword();
try {
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
keyStore(clientCertKeystore, clientKeystorePassword);
} catch (Exception e) {
throw new RuntimeException("Failed to load keystore", e);
}
}
int size = 10;
if (adapterConfig.getConnectionPoolSize() > 0)
size = adapterConfig.getConnectionPoolSize();
HttpClientBuilder.HostnameVerificationPolicy policy = HttpClientBuilder.HostnameVerificationPolicy.WILDCARD;
if (adapterConfig.isAllowAnyHostname())
policy = HttpClientBuilder.HostnameVerificationPolicy.ANY;
connectionPoolSize(size);
hostnameVerification(policy);
if (adapterConfig.isDisableTrustManager()) {
disableTrustManager();
} else {
trustStore(truststore);
}
return build();
}
}
@@ -1,360 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.client.HttpClient;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.KeycloakUriBuilder;
import java.net.URI;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakDeployment {
private static final Logger log = Logger.getLogger(KeycloakDeployment.class);
protected RelativeUrlsUsed relativeUrls;
protected String realm;
protected volatile PublicKey realmKey;
protected String authServerBaseUrl;
protected String realmInfoUrl;
protected KeycloakUriBuilder authUrl;
protected String tokenUrl;
protected KeycloakUriBuilder logoutUrl;
protected String accountUrl;
protected String registerNodeUrl;
protected String unregisterNodeUrl;
protected String principalAttribute = "sub";
protected String resourceName;
protected boolean bearerOnly;
protected boolean enableBasicAuth;
protected boolean publicClient;
protected Map<String, Object> resourceCredentials = new HashMap<>();
protected ClientCredentialsProvider clientAuthenticator;
protected HttpClient client;
protected String scope;
protected SslRequired sslRequired = SslRequired.ALL;
protected TokenStore tokenStore = TokenStore.SESSION;
protected String stateCookieName = "OAuth_Token_Request_State";
protected boolean useResourceRoleMappings;
protected boolean cors;
protected int corsMaxAge = -1;
protected String corsAllowedHeaders;
protected String corsAllowedMethods;
protected boolean exposeToken;
protected boolean alwaysRefreshToken;
protected boolean registerNodeAtStartup;
protected int registerNodePeriod;
protected boolean turnOffChangeSessionIdOnLogin;
protected volatile int notBefore;
public KeycloakDeployment() {
}
public boolean isConfigured() {
return getRealm() != null && getRealmKey() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
}
public String getResourceName() {
return resourceName;
}
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public PublicKey getRealmKey() {
return realmKey;
}
public void setRealmKey(PublicKey realmKey) {
this.realmKey = realmKey;
}
public String getAuthServerBaseUrl() {
return authServerBaseUrl;
}
public void setAuthServerBaseUrl(AdapterConfig config) {
this.authServerBaseUrl = config.getAuthServerUrl();
if (authServerBaseUrl == null) return;
URI authServerUri = URI.create(authServerBaseUrl);
if (authServerUri.getHost() == null) {
relativeUrls = RelativeUrlsUsed.ALWAYS;
} else {
// We have absolute URI in config
relativeUrls = RelativeUrlsUsed.NEVER;
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
resolveUrls(serverBuilder);
}
}
/**
* @param authUrlBuilder absolute URI
*/
protected void resolveUrls(KeycloakUriBuilder authUrlBuilder) {
if (log.isDebugEnabled()) {
log.debug("resolveUrls");
}
String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString();
authUrl = KeycloakUriBuilder.fromUri(login);
realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString();
tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString();
logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString());
accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString();
registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString();
unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString();
}
public RelativeUrlsUsed getRelativeUrls() {
return relativeUrls;
}
public String getRealmInfoUrl() {
return realmInfoUrl;
}
public KeycloakUriBuilder getAuthUrl() {
return authUrl;
}
public String getTokenUrl() {
return tokenUrl;
}
public KeycloakUriBuilder getLogoutUrl() {
return logoutUrl;
}
public String getAccountUrl() {
return accountUrl;
}
public String getRegisterNodeUrl() {
return registerNodeUrl;
}
public String getUnregisterNodeUrl() {
return unregisterNodeUrl;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
public boolean isBearerOnly() {
return bearerOnly;
}
public void setBearerOnly(boolean bearerOnly) {
this.bearerOnly = bearerOnly;
}
public boolean isEnableBasicAuth() {
return enableBasicAuth;
}
public void setEnableBasicAuth(boolean enableBasicAuth) {
this.enableBasicAuth = enableBasicAuth;
}
public boolean isPublicClient() {
return publicClient;
}
public void setPublicClient(boolean publicClient) {
this.publicClient = publicClient;
}
public Map<String, Object> getResourceCredentials() {
return resourceCredentials;
}
public void setResourceCredentials(Map<String, Object> resourceCredentials) {
this.resourceCredentials = resourceCredentials;
}
public ClientCredentialsProvider getClientAuthenticator() {
return clientAuthenticator;
}
public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
this.clientAuthenticator = clientAuthenticator;
}
public HttpClient getClient() {
return client;
}
public void setClient(HttpClient client) {
this.client = client;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public SslRequired getSslRequired() {
return sslRequired;
}
public void setSslRequired(SslRequired sslRequired) {
this.sslRequired = sslRequired;
}
public TokenStore getTokenStore() {
return tokenStore;
}
public void setTokenStore(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
public String getStateCookieName() {
return stateCookieName;
}
public void setStateCookieName(String stateCookieName) {
this.stateCookieName = stateCookieName;
}
public boolean isUseResourceRoleMappings() {
return useResourceRoleMappings;
}
public void setUseResourceRoleMappings(boolean useResourceRoleMappings) {
this.useResourceRoleMappings = useResourceRoleMappings;
}
public boolean isCors() {
return cors;
}
public void setCors(boolean cors) {
this.cors = cors;
}
public int getCorsMaxAge() {
return corsMaxAge;
}
public void setCorsMaxAge(int corsMaxAge) {
this.corsMaxAge = corsMaxAge;
}
public String getCorsAllowedHeaders() {
return corsAllowedHeaders;
}
public void setCorsAllowedHeaders(String corsAllowedHeaders) {
this.corsAllowedHeaders = corsAllowedHeaders;
}
public String getCorsAllowedMethods() {
return corsAllowedMethods;
}
public void setCorsAllowedMethods(String corsAllowedMethods) {
this.corsAllowedMethods = corsAllowedMethods;
}
public boolean isExposeToken() {
return exposeToken;
}
public void setExposeToken(boolean exposeToken) {
this.exposeToken = exposeToken;
}
public int getNotBefore() {
return notBefore;
}
public void setNotBefore(int notBefore) {
this.notBefore = notBefore;
}
public boolean isAlwaysRefreshToken() {
return alwaysRefreshToken;
}
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
this.alwaysRefreshToken = alwaysRefreshToken;
}
public boolean isRegisterNodeAtStartup() {
return registerNodeAtStartup;
}
public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
this.registerNodeAtStartup = registerNodeAtStartup;
}
public int getRegisterNodePeriod() {
return registerNodePeriod;
}
public void setRegisterNodePeriod(int registerNodePeriod) {
this.registerNodePeriod = registerNodePeriod;
}
public String getPrincipalAttribute() {
return principalAttribute;
}
public void setPrincipalAttribute(String principalAttribute) {
this.principalAttribute = principalAttribute;
}
public boolean isTurnOffChangeSessionIdOnLogin() {
return turnOffChangeSessionIdOnLogin;
}
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
this.turnOffChangeSessionIdOnLogin = turnOffChangeSessionIdOnLogin;
}
}
@@ -1,139 +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 com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jboss.logging.Logger;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.common.util.PemUtils;
import org.keycloak.util.SystemPropertiesJsonParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.PublicKey;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakDeploymentBuilder {
private static final Logger log = Logger.getLogger(KeycloakDeploymentBuilder.class);
protected KeycloakDeployment deployment = new KeycloakDeployment();
protected KeycloakDeploymentBuilder() {
}
protected KeycloakDeployment internalBuild(AdapterConfig adapterConfig) {
if (adapterConfig.getRealm() == null) throw new RuntimeException("Must set 'realm' in config");
deployment.setRealm(adapterConfig.getRealm());
String resource = adapterConfig.getResource();
if (resource == null) throw new RuntimeException("Must set 'resource' in config");
deployment.setResourceName(resource);
String realmKeyPem = adapterConfig.getRealmKey();
if (realmKeyPem != null) {
PublicKey realmKey;
try {
realmKey = PemUtils.decodePublicKey(realmKeyPem);
} catch (Exception e) {
throw new RuntimeException(e);
}
deployment.setRealmKey(realmKey);
}
if (adapterConfig.getSslRequired() != null) {
deployment.setSslRequired(SslRequired.valueOf(adapterConfig.getSslRequired().toUpperCase()));
} else {
deployment.setSslRequired(SslRequired.EXTERNAL);
}
if (adapterConfig.getTokenStore() != null) {
deployment.setTokenStore(TokenStore.valueOf(adapterConfig.getTokenStore().toUpperCase()));
} else {
deployment.setTokenStore(TokenStore.SESSION);
}
if (adapterConfig.getPrincipalAttribute() != null) deployment.setPrincipalAttribute(adapterConfig.getPrincipalAttribute());
deployment.setResourceCredentials(adapterConfig.getCredentials());
deployment.setClientAuthenticator(ClientCredentialsProviderUtils.bootstrapClientAuthenticator(deployment));
deployment.setPublicClient(adapterConfig.isPublicClient());
deployment.setUseResourceRoleMappings(adapterConfig.isUseResourceRoleMappings());
deployment.setExposeToken(adapterConfig.isExposeToken());
if (adapterConfig.isCors()) {
deployment.setCors(true);
deployment.setCorsMaxAge(adapterConfig.getCorsMaxAge());
deployment.setCorsAllowedHeaders(adapterConfig.getCorsAllowedHeaders());
deployment.setCorsAllowedMethods(adapterConfig.getCorsAllowedMethods());
}
deployment.setBearerOnly(adapterConfig.isBearerOnly());
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
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");
}
if (realmKeyPem == null || !deployment.isBearerOnly() || deployment.isEnableBasicAuth() || deployment.isRegisterNodeAtStartup() || deployment.getRegisterNodePeriod() != -1) {
deployment.setClient(new HttpClientBuilder().build(adapterConfig));
}
if (adapterConfig.getAuthServerUrl() == null && (!deployment.isBearerOnly() || realmKeyPem == null)) {
throw new RuntimeException("You must specify auth-url");
}
deployment.setAuthServerBaseUrl(adapterConfig);
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
}
log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls());
return deployment;
}
public static KeycloakDeployment build(InputStream is) {
AdapterConfig adapterConfig = loadAdapterConfig(is);
return new KeycloakDeploymentBuilder().internalBuild(adapterConfig);
}
public static AdapterConfig loadAdapterConfig(InputStream is) {
ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
mapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT);
AdapterConfig adapterConfig;
try {
adapterConfig = mapper.readValue(is, AdapterConfig.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
return adapterConfig;
}
public static KeycloakDeployment build(AdapterConfig adapterConfig) {
return new KeycloakDeploymentBuilder().internalBuild(adapterConfig);
}
}
@@ -1,368 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.RSATokenVerifier;
import org.keycloak.adapters.spi.AdapterSessionStore;
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.constants.AdapterConstants;
import org.keycloak.enums.TokenStore;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.IDToken;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.UriUtils;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OAuthRequestAuthenticator {
private static final Logger log = Logger.getLogger(OAuthRequestAuthenticator.class);
protected KeycloakDeployment deployment;
protected RequestAuthenticator reqAuthenticator;
protected int sslRedirectPort;
protected AdapterSessionStore tokenStore;
protected String tokenString;
protected String idTokenString;
protected IDToken idToken;
protected AccessToken token;
protected HttpFacade facade;
protected AuthChallenge challenge;
protected String refreshToken;
protected String strippedOauthParametersRequestUri;
public OAuthRequestAuthenticator(RequestAuthenticator requestAuthenticator, HttpFacade facade, KeycloakDeployment deployment, int sslRedirectPort, AdapterSessionStore tokenStore) {
this.reqAuthenticator = requestAuthenticator;
this.facade = facade;
this.deployment = deployment;
this.sslRedirectPort = sslRedirectPort;
this.tokenStore = tokenStore;
}
public AuthChallenge getChallenge() {
return challenge;
}
public String getTokenString() {
return tokenString;
}
public AccessToken getToken() {
return token;
}
public String getRefreshToken() {
return refreshToken;
}
public String getIdTokenString() {
return idTokenString;
}
public void setIdTokenString(String idTokenString) {
this.idTokenString = idTokenString;
}
public IDToken getIdToken() {
return idToken;
}
public void setIdToken(IDToken idToken) {
this.idToken = idToken;
}
public String getStrippedOauthParametersRequestUri() {
return strippedOauthParametersRequestUri;
}
public void setStrippedOauthParametersRequestUri(String strippedOauthParametersRequestUri) {
this.strippedOauthParametersRequestUri = strippedOauthParametersRequestUri;
}
protected String getRequestUrl() {
return facade.getRequest().getURI();
}
protected boolean isRequestSecure() {
return facade.getRequest().isSecure();
}
protected OIDCHttpFacade.Cookie getCookie(String cookieName) {
return facade.getRequest().getCookie(cookieName);
}
protected String getCookieValue(String cookieName) {
OIDCHttpFacade.Cookie cookie = getCookie(cookieName);
if (cookie == null) return null;
return cookie.getValue();
}
protected String getQueryParamValue(String paramName) {
return facade.getRequest().getQueryParamValue(paramName);
}
protected String getError() {
return getQueryParamValue(OAuth2Constants.ERROR);
}
protected String getCode() {
return getQueryParamValue(OAuth2Constants.CODE);
}
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) {
// disabled?
return null;
}
KeycloakUriBuilder secureUrl = KeycloakUriBuilder.fromUri(url).scheme("https").port(-1);
if (port != 443) secureUrl.port(port);
url = secureUrl.build().toString();
}
String loginHint = getQueryParamValue("login_hint");
url = UriUtils.stripQueryParam(url,"login_hint");
String idpHint = getQueryParamValue(AdapterConstants.KC_IDP_HINT);
url = UriUtils.stripQueryParam(url, AdapterConstants.KC_IDP_HINT);
String scope = getQueryParamValue(OAuth2Constants.SCOPE);
url = UriUtils.stripQueryParam(url, OAuth2Constants.SCOPE);
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
.queryParam(OAuth2Constants.REDIRECT_URI, url)
.queryParam(OAuth2Constants.STATE, state)
.queryParam("login", "true");
if(loginHint != null && loginHint.length() > 0){
redirectUriBuilder.queryParam("login_hint",loginHint);
}
if (idpHint != null && idpHint.length() > 0) {
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
}
if (scope != null && scope.length() > 0) {
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
}
return redirectUriBuilder.build().toString();
}
protected int sslRedirectPort() {
return sslRedirectPort;
}
protected static final AtomicLong counter = new AtomicLong();
protected String getStateCode() {
return counter.getAndIncrement() + "/" + AdapterUtils.generateId();
}
protected AuthChallenge loginRedirect() {
final String state = getStateCode();
final String redirect = getRedirectUri(state);
if (redirect == null) {
return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
}
return new AuthChallenge() {
@Override
public int getResponseCode() {
return 0;
}
@Override
public boolean challenge(HttpFacade exchange) {
tokenStore.saveRequest();
log.debug("Sending redirect to login page: " + redirect);
exchange.getResponse().setStatus(302);
exchange.getResponse().setCookie(deployment.getStateCookieName(), state, /* need to set path? */ null, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
exchange.getResponse().setHeader("Location", redirect);
return true;
}
};
}
protected AuthChallenge checkStateCookie() {
OIDCHttpFacade.Cookie stateCookie = getCookie(deployment.getStateCookieName());
if (stateCookie == null) {
log.warn("No state cookie");
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
// reset the cookie
log.debug("** reseting application state cookie");
facade.getResponse().resetCookie(deployment.getStateCookieName(), stateCookie.getPath());
String stateCookieValue = getCookieValue(deployment.getStateCookieName());
String state = getQueryParamValue(OAuth2Constants.STATE);
if (state == null) {
log.warn("state parameter was null");
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
if (!state.equals(stateCookieValue)) {
log.warn("state parameter invalid");
log.warn("cookie: " + stateCookieValue);
log.warn("queryParam: " + state);
return challenge(400, OIDCAuthenticationError.Reason.INVALID_STATE_COOKIE, null);
}
return null;
}
public AuthOutcome authenticate() {
String code = getCode();
if (code == null) {
log.debug("there was no code");
String error = getError();
if (error != null) {
// todo how do we send a response?
log.warn("There was an error: " + error);
challenge = challenge(400, OIDCAuthenticationError.Reason.OAUTH_ERROR, error);
return AuthOutcome.FAILED;
} else {
log.debug("redirecting to auth server");
challenge = loginRedirect();
return AuthOutcome.NOT_ATTEMPTED;
}
} else {
log.debug("there was a code, resolving");
challenge = resolveCode(code);
if (challenge != null) {
return AuthOutcome.FAILED;
}
return AuthOutcome.AUTHENTICATED;
}
}
protected AuthChallenge challenge(final int code, final OIDCAuthenticationError.Reason reason, final String description) {
return new AuthChallenge() {
@Override
public int getResponseCode() {
return code;
}
@Override
public boolean challenge(HttpFacade exchange) {
OIDCAuthenticationError error = new OIDCAuthenticationError(reason, description);
exchange.getRequest().setError(error);
exchange.getResponse().sendError(code);
return true;
}
};
}
/**
* Start or continue the oauth login process.
* <p/>
* if code query parameter is not present, then browser is redirected to authUrl. The redirect URL will be
* the URL of the current request.
* <p/>
* If code query parameter is present, then an access token is obtained by invoking a secure request to the codeUrl.
* If the access token is obtained, the browser is again redirected to the current request URL, but any OAuth
* protocol specific query parameters are removed.
*
* @return null if an access token was obtained, otherwise a challenge is returned
*/
protected AuthChallenge resolveCode(String code) {
// abort if not HTTPS
if (!isRequestSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.error("Adapter requires SSL. Request: " + facade.getRequest().getURI());
return challenge(403, OIDCAuthenticationError.Reason.SSL_REQUIRED, null);
}
log.debug("checking state cookie for after code");
AuthChallenge challenge = checkStateCookie();
if (challenge != null) return challenge;
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, strippedOauthParametersRequestUri, httpSessionId);
} catch (ServerRequest.HttpFailure failure) {
log.error("failed to turn code into token");
log.error("status from server: " + failure.getStatus());
if (failure.getStatus() == 400 && failure.getError() != null) {
log.error(" " + failure.getError());
}
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
} catch (IOException e) {
log.error("failed to turn code into token", e);
return challenge(403, OIDCAuthenticationError.Reason.CODE_TO_TOKEN_FAILURE, null);
}
tokenString = tokenResponse.getToken();
refreshToken = tokenResponse.getRefreshToken();
idTokenString = tokenResponse.getIdToken();
try {
token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
if (idTokenString != null) {
try {
JWSInput input = new JWSInput(idTokenString);
idToken = input.readJsonContent(IDToken.class);
} catch (JWSInputException e) {
throw new VerificationException();
}
}
log.debug("Token Verification succeeded!");
} catch (VerificationException e) {
log.error("failed verification of token: " + e.getMessage());
return challenge(403, OIDCAuthenticationError.Reason.INVALID_TOKEN, null);
}
if (tokenResponse.getNotBeforePolicy() > deployment.getNotBefore()) {
deployment.setNotBefore(tokenResponse.getNotBeforePolicy());
}
if (token.getIssuedAt() < deployment.getNotBefore()) {
log.error("Stale token");
return challenge(403, OIDCAuthenticationError.Reason.STALE_TOKEN, null);
}
log.debug("successful authenticated");
return null;
}
/**
* strip out unwanted query parameters and redirect so bookmarks don't retain oauth protocol bits
*/
protected String stripOauthParametersFromRedirect() {
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(facade.getRequest().getURI())
.replaceQueryParam(OAuth2Constants.CODE, null)
.replaceQueryParam(OAuth2Constants.STATE, null);
return builder.build().toString();
}
}
@@ -1,63 +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.spi.AuthenticationError;
/**
* Object that describes the OIDC error that happened.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCAuthenticationError implements AuthenticationError {
public static enum Reason {
NO_BEARER_TOKEN,
NO_REDIRECT_URI,
INVALID_STATE_COOKIE,
OAUTH_ERROR,
SSL_REQUIRED,
CODE_TO_TOKEN_FAILURE,
INVALID_TOKEN,
STALE_TOKEN,
NO_AUTHORIZATION_HEADER
}
private Reason reason;
private String description;
public OIDCAuthenticationError(Reason reason, String description) {
this.reason = reason;
this.description = description;
}
public Reason getReason() {
return reason;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return "OIDCAuthenticationError [reason=" + reason + ", description=" + description + "]";
}
}
@@ -1,32 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.spi.HttpFacade;
/**
* Bridge between core adapter and HTTP Engine
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface OIDCHttpFacade extends HttpFacade {
KeycloakSecurityContext getSecurityContext();
}
@@ -1,29 +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.KeycloakSecurityContext;
import org.keycloak.adapters.spi.KeycloakAccount;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface OidcKeycloakAccount extends KeycloakAccount {
KeycloakSecurityContext getKeycloakSecurityContext();
}
@@ -1,177 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.HttpFacade;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class RequestAuthenticator {
protected static Logger log = Logger.getLogger(RequestAuthenticator.class);
protected HttpFacade facade;
protected AuthChallenge challenge;
protected KeycloakDeployment deployment;
protected AdapterTokenStore tokenStore;
protected int sslRedirectPort;
public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
this.facade = facade;
this.deployment = deployment;
this.tokenStore = tokenStore;
this.sslRedirectPort = sslRedirectPort;
}
public RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment) {
this.facade = facade;
this.deployment = deployment;
}
public AuthChallenge getChallenge() {
return challenge;
}
public AuthOutcome authenticate() {
if (log.isTraceEnabled()) {
log.trace("--> authenticate()");
}
BearerTokenRequestAuthenticator bearer = createBearerTokenAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try bearer");
}
AuthOutcome outcome = bearer.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = bearer.getChallenge();
log.debug("Bearer FAILED");
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
if (verifySSL()) return AuthOutcome.FAILED;
completeAuthentication(bearer, "KEYCLOAK");
log.debug("Bearer AUTHENTICATED");
return AuthOutcome.AUTHENTICATED;
}
if (deployment.isEnableBasicAuth()) {
BasicAuthRequestAuthenticator basicAuth = createBasicAuthAuthenticator();
if (log.isTraceEnabled()) {
log.trace("try basic auth");
}
outcome = basicAuth.authenticate(facade);
if (outcome == AuthOutcome.FAILED) {
challenge = basicAuth.getChallenge();
log.debug("BasicAuth FAILED");
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.AUTHENTICATED) {
log.debug("BasicAuth AUTHENTICATED");
completeAuthentication(basicAuth, "BASIC");
return AuthOutcome.AUTHENTICATED;
}
}
if (deployment.isBearerOnly()) {
challenge = bearer.getChallenge();
log.debug("NOT_ATTEMPTED: bearer only");
return AuthOutcome.NOT_ATTEMPTED;
}
if (log.isTraceEnabled()) {
log.trace("try oauth");
}
if (tokenStore.isCached(this)) {
if (verifySSL()) return AuthOutcome.FAILED;
log.debug("AUTHENTICATED: was cached");
return AuthOutcome.AUTHENTICATED;
}
OAuthRequestAuthenticator oauth = createOAuthAuthenticator();
outcome = oauth.authenticate();
if (outcome == AuthOutcome.FAILED) {
challenge = oauth.getChallenge();
return AuthOutcome.FAILED;
} else if (outcome == AuthOutcome.NOT_ATTEMPTED) {
challenge = oauth.getChallenge();
return AuthOutcome.NOT_ATTEMPTED;
}
if (verifySSL()) return AuthOutcome.FAILED;
completeAuthentication(oauth);
// redirect to strip out access code and state query parameters
facade.getResponse().setHeader("Location", oauth.getStrippedOauthParametersRequestUri());
facade.getResponse().setStatus(302);
facade.getResponse().end();
log.debug("AUTHENTICATED");
return AuthOutcome.AUTHENTICATED;
}
protected boolean verifySSL() {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warn("SSL is required to authenticate");
return true;
}
return false;
}
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
return new BearerTokenRequestAuthenticator(deployment);
}
protected BasicAuthRequestAuthenticator createBasicAuthAuthenticator() {
return new BasicAuthRequestAuthenticator(deployment);
}
protected void completeAuthentication(OAuthRequestAuthenticator oauth) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, tokenStore, oauth.getTokenString(), oauth.getToken(), oauth.getIdTokenString(), oauth.getIdToken(), oauth.getRefreshToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, oauth.getToken()), session);
completeOAuthAuthentication(principal);
log.debugv("User ''{0}'' invoking ''{1}'' on client ''{2}''", principal.getName(), facade.getRequest().getURI(), deployment.getResourceName());
}
protected abstract void completeOAuthAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal);
protected abstract void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method);
/**
* After code is received, we change the session id if possible to guard against https://www.owasp.org/index.php/Session_Fixation
* @param create
* @return
*/
protected abstract String changeHttpSessionId(boolean create);
protected void completeAuthentication(BearerTokenRequestAuthenticator bearer, String method) {
RefreshableKeycloakSecurityContext session = new RefreshableKeycloakSecurityContext(deployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, bearer.getToken()), session);
completeBearerAuthentication(principal, method);
log.debugv("User ''{0}'' invoking ''{1}'' on client ''{2}''", principal.getName(), facade.getRequest().getURI(), deployment.getResourceName());
}
}
@@ -1,239 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.common.util.HostUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.StreamUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ServerRequest {
public static class HttpFailure extends Exception {
private int status;
private String error;
public HttpFailure(int status, String error) {
this.status = status;
this.error = error;
}
public int getStatus() {
return status;
}
public String getError() {
return error;
}
}
public static void invokeLogout(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
HttpClient client = deployment.getClient();
URI uri = deployment.getLogoutUrl().clone().build();
List<NameValuePair> formparams = new ArrayList<>();
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
HttpPost post = new HttpPost(uri);
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 204) {
error(status, entity);
}
if (entity == null) {
return;
}
InputStream is = entity.getContent();
if (is != null) is.close();
}
public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<>();
redirectUri = stripOauthParametersFromRedirect(redirectUri);
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code"));
formparams.add(new BasicNameValuePair(OAuth2Constants.CODE, code));
formparams.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, redirectUri));
if (sessionId != null) {
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_STATE, sessionId));
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_SESSION_HOST, HostUtils.getHostName()));
}
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
error(status, entity);
}
if (entity == null) {
throw new HttpFailure(status, null);
}
InputStream is = entity.getContent();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
try {
return JsonSerialization.readValue(json, AccessTokenResponse.class);
} catch (IOException e) {
throw new IOException(json, e);
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure {
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN));
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
HttpPost post = new HttpPost(deployment.getTokenUrl());
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
error(status, entity);
}
if (entity == null) {
throw new HttpFailure(status, null);
}
InputStream is = entity.getContent();
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
int c;
while ((c = is.read()) != -1) {
os.write(c);
}
byte[] bytes = os.toByteArray();
String json = new String(bytes);
try {
return JsonSerialization.readValue(json, AccessTokenResponse.class);
} catch (IOException e) {
throw new IOException(json, e);
}
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
public static void invokeRegisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
String registerNodeUrl = deployment.getRegisterNodeUrl();
invokeClientManagementRequest(deployment, host, registerNodeUrl);
}
public static void invokeUnregisterNode(KeycloakDeployment deployment, String host) throws HttpFailure, IOException {
String unregisterNodeUrl = deployment.getUnregisterNodeUrl();
invokeClientManagementRequest(deployment, host, unregisterNodeUrl);
}
public static void invokeClientManagementRequest(KeycloakDeployment deployment, String host, String endpointUrl) throws HttpFailure, IOException {
if (endpointUrl == null) {
throw new IOException("You need to configure URI for register/unregister node for application " + deployment.getResourceName());
}
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(AdapterConstants.CLIENT_CLUSTER_HOST, host));
HttpPost post = new HttpPost(endpointUrl);
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpResponse response = deployment.getClient().execute(post);
int status = response.getStatusLine().getStatusCode();
if (status != 204) {
HttpEntity entity = response.getEntity();
error(status, entity);
}
}
public static void error(int status, HttpEntity entity) throws HttpFailure, IOException {
String body = null;
if (entity != null) {
InputStream is = entity.getContent();
try {
body = StreamUtil.readString(is);
} catch (IOException e) {
} finally {
try {
is.close();
} catch (IOException ignored) {
}
}
}
throw new HttpFailure(status, body);
}
protected static String stripOauthParametersFromRedirect(String uri) {
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(uri)
.replaceQueryParam(OAuth2Constants.CODE, null)
.replaceQueryParam(OAuth2Constants.STATE, null);
return builder.build().toString();
}
}
@@ -1,134 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.HttpHost;
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.protocol.HttpContext;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.AccessController;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedExceptionAction;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class SniSSLSocketFactory extends SSLSocketFactory {
private static Logger log = Logger.getLogger(SniSSLSocketFactory.class.getName());
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, HostNameResolver nameResolver) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, nameResolver);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(String algorithm, KeyStore keystore, String keyPassword, KeyStore truststore, SecureRandom random, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(algorithm, keystore, keyPassword, truststore, random, hostnameVerifier);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword, KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword, truststore);
}
public SniSSLSocketFactory(KeyStore keystore, String keystorePassword) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(keystore, keystorePassword);
}
public SniSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy, X509HostnameVerifier hostnameVerifier) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy, hostnameVerifier);
}
public SniSSLSocketFactory(TrustStrategy trustStrategy) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(trustStrategy);
}
public SniSSLSocketFactory(SSLContext sslContext) {
super(sslContext);
}
public SniSSLSocketFactory(SSLContext sslContext, HostNameResolver nameResolver) {
super(sslContext, nameResolver);
}
public SniSSLSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
super(sslContext, hostnameVerifier);
}
public SniSSLSocketFactory(SSLContext sslContext, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, hostnameVerifier);
}
public SniSSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory, String[] supportedProtocols, String[] supportedCipherSuites, X509HostnameVerifier hostnameVerifier) {
super(socketfactory, supportedProtocols, supportedCipherSuites, hostnameVerifier);
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException {
return super.connectSocket(connectTimeout, applySNI(socket, host.getHostName()), host, remoteAddress, localAddress, context);
}
@Override
public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context) throws IOException {
return super.createLayeredSocket(applySNI(socket, target), target, port, context);
}
private Socket applySNI(final Socket socket, String hostname) {
if (socket instanceof SSLSocket) {
try {
Method setHostMethod = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
public Method run() throws NoSuchMethodException {
return socket.getClass().getMethod("setHost", String.class);
}
});
setHostMethod.invoke(socket, hostname);
log.finest("Applied SNI to socket for: " + hostname);
} catch (Exception e) {
log.log(Level.WARNING, "Failed to apply SNI to SSLSocket", e);
}
}
return socket;
}
}
@@ -1,78 +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.authentication;
import java.util.Map;
import org.keycloak.adapters.KeycloakDeployment;
/**
* The simple SPI for authenticating clients/applications . It's used by adapter during all OIDC backchannel requests to Keycloak server
* (codeToToken exchange, refresh token or backchannel logout) . You can also use it in your application during direct access grants or service account request
* (See the service-account example from Keycloak demo for more info)
*
* When you implement this SPI on the adapter (application) side, you also need to implement org.keycloak.authentication.ClientAuthenticator on the server side,
* so your server is able to authenticate client
*
* You must specify a file
* META-INF/services/org.keycloak.adapters.authentication.ClientCredentialsProvider in the WAR that this class is contained in (or in the JAR that is attached to the WEB-INF/lib or as jboss module
* if you want to share the implementation among more WARs).
*
* NOTE: The SPI is not finished and method signatures are still subject to change in future versions (for example to support
* authentication with client certificate)
*
* @see ClientIdAndSecretCredentialsProvider
* @see JWTClientCredentialsProvider
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ClientCredentialsProvider {
/**
* Return the ID of the provider. Use this ID in the keycloak.json configuration as the subelement of the "credentials" element
*
* For example if your provider has ID "kerberos-keytab" , use the configuration like this in keycloak.json
*
* "credentials": {
*
* "kerberos-keytab": {
* "keytab": "/tmp/foo"
* }
* }
*
* @return
*/
String getId();
/**
* Called by adapter during deployment of your application. You can for example read configuration and init your authenticator here
*
* @param deployment the adapter configuration
* @param config the configuration of your provider read from keycloak.json . For the kerberos-keytab example above, it will return map with the single key "keytab" with value "/tmp/foo"
*/
void init(KeycloakDeployment deployment, Object config);
/**
* Called every time adapter needs to perform backchannel request
*
* @param deployment Fully resolved deployment
* @param requestHeaders You should put any HTTP request headers you want to use for authentication of client. These headers will be attached to the HTTP request sent to Keycloak server
* @param formParams You should put any request parameters you want to use for authentication of client. These parameters will be attached to the HTTP request sent to Keycloak server
*/
void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams);
}
@@ -1,116 +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.authentication;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.adapters.KeycloakDeployment;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientCredentialsProviderUtils {
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
public static ClientCredentialsProvider bootstrapClientAuthenticator(KeycloakDeployment deployment) {
String clientId = deployment.getResourceName();
Map<String, Object> clientCredentials = deployment.getResourceCredentials();
String authenticatorId;
if (clientCredentials == null || clientCredentials.isEmpty()) {
authenticatorId = ClientIdAndSecretCredentialsProvider.PROVIDER_ID;
} else {
authenticatorId = (String) clientCredentials.get("provider");
if (authenticatorId == null) {
// If there is just one credential type, use provider from it
if (clientCredentials.size() == 1) {
authenticatorId = clientCredentials.keySet().iterator().next();
} else {
throw new RuntimeException("Can't identify clientAuthenticator from the configuration of client '" + clientId + "' . Check your adapter configurations");
}
}
}
logger.debugf("Using provider '%s' for authentication of client '%s'", authenticatorId, clientId);
Map<String, ClientCredentialsProvider> authenticators = new HashMap<>();
loadAuthenticators(authenticators, ClientCredentialsProviderUtils.class.getClassLoader());
loadAuthenticators(authenticators, Thread.currentThread().getContextClassLoader());
ClientCredentialsProvider authenticator = authenticators.get(authenticatorId);
if (authenticator == null) {
throw new RuntimeException("Couldn't find ClientCredentialsProvider implementation class with id: " + authenticatorId + ". Loaded authentication providers: " + authenticators.keySet());
}
Object config = (clientCredentials==null) ? null : clientCredentials.get(authenticatorId);
authenticator.init(deployment, config);
return authenticator;
}
private static void loadAuthenticators(Map<String, ClientCredentialsProvider> authenticators, ClassLoader classLoader) {
Iterator<ClientCredentialsProvider> iterator = ServiceLoader.load(ClientCredentialsProvider.class, classLoader).iterator();
while (iterator.hasNext()) {
try {
ClientCredentialsProvider authenticator = iterator.next();
logger.debugf("Loaded clientCredentialsProvider %s", authenticator.getId());
authenticators.put(authenticator.getId(), authenticator);
} catch (ServiceConfigurationError e) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to load clientCredentialsProvider with classloader: " + classLoader, e);
}
}
}
}
/**
* Use this method when calling backchannel request directly from your application. See service-account example from demo for more details
*/
public static void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formparams) {
ClientCredentialsProvider authenticator = deployment.getClientAuthenticator();
authenticator.setClientCredentials(deployment, requestHeaders, formparams);
}
/**
* Don't use directly from your JEE apps to avoid HttpClient linkage errors! Instead use the method {@link #setClientCredentials(KeycloakDeployment, Map, Map)}
*/
public static void setClientCredentials(KeycloakDeployment deployment, HttpPost post, List<NameValuePair> formparams) {
Map<String, String> reqHeaders = new HashMap<>();
Map<String, String> reqParams = new HashMap<>();
setClientCredentials(deployment, reqHeaders, reqParams);
for (Map.Entry<String, String> header : reqHeaders.entrySet()) {
post.setHeader(header.getKey(), header.getValue());
}
for (Map.Entry<String, String> param : reqParams.entrySet()) {
formparams.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
}
}
@@ -1,66 +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.authentication;
import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.BasicAuthHelper;
/**
* Traditional OAuth2 authentication of clients based on client_id and client_secret
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientIdAndSecretCredentialsProvider implements ClientCredentialsProvider {
private static Logger logger = Logger.getLogger(ClientCredentialsProviderUtils.class);
public static final String PROVIDER_ID = CredentialRepresentation.SECRET;
private String clientSecret;
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public void init(KeycloakDeployment deployment, Object config) {
clientSecret = (String) config;
}
@Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
String clientId = deployment.getResourceName();
if (!deployment.isPublicClient()) {
if (clientSecret != null) {
String authorization = BasicAuthHelper.createHeader(clientId, clientSecret);
requestHeaders.put("Authorization", authorization);
} else {
logger.warnf("Client '%s' doesn't have secret available", clientId);
}
} else {
formParams.put(OAuth2Constants.CLIENT_ID, clientId);
}
}
}
@@ -1,137 +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.authentication;
import java.security.PrivateKey;
import java.util.Map;
import org.keycloak.OAuth2Constants;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.Time;
/**
* Client authentication based on JWT signed by client private key .
* See <a href="https://tools.ietf.org/html/rfc7519">specs</a> for more details.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JWTClientCredentialsProvider implements ClientCredentialsProvider {
public static final String PROVIDER_ID = "jwt";
private PrivateKey privateKey;
private int tokenTimeout;
@Override
public String getId() {
return PROVIDER_ID;
}
public void setPrivateKey(PrivateKey privateKey) {
this.privateKey = privateKey;
}
public void setTokenTimeout(int tokenTimeout) {
this.tokenTimeout = tokenTimeout;
}
@Override
public void init(KeycloakDeployment deployment, Object config) {
if (config == null || !(config instanceof Map)) {
throw new RuntimeException("Configuration of jwt credentials is missing or incorrect for client '" + deployment.getResourceName() + "'. Check your adapter configuration");
}
Map<String, Object> cfg = (Map<String, Object>) config;
String clientKeystoreFile = (String) cfg.get("client-keystore-file");
if (clientKeystoreFile == null) {
throw new RuntimeException("Missing parameter client-keystore-file in configuration of jwt for client " + deployment.getResourceName());
}
String clientKeystoreType = (String) cfg.get("client-keystore-type");
KeystoreUtil.KeystoreFormat clientKeystoreFormat = clientKeystoreType==null ? KeystoreUtil.KeystoreFormat.JKS : Enum.valueOf(KeystoreUtil.KeystoreFormat.class, clientKeystoreType.toUpperCase());
String clientKeystorePassword = (String) cfg.get("client-keystore-password");
if (clientKeystorePassword == null) {
throw new RuntimeException("Missing parameter client-keystore-password in configuration of jwt for client " + deployment.getResourceName());
}
String clientKeyPassword = (String) cfg.get("client-key-password");
if (clientKeyPassword == null) {
clientKeyPassword = clientKeystorePassword;
}
String clientKeyAlias = (String) cfg.get("client-key-alias");
if (clientKeyAlias == null) {
clientKeyAlias = deployment.getResourceName();
}
this.privateKey = KeystoreUtil.loadPrivateKeyFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
this.tokenTimeout = asInt(cfg, "token-timeout", 10);
}
// TODO: Generic method for this?
private Integer asInt(Map<String, Object> cfg, String cfgKey, int defaultValue) {
Object cfgObj = cfg.get(cfgKey);
if (cfgObj == null) {
return defaultValue;
}
if (cfgObj instanceof String) {
return Integer.parseInt(cfgObj.toString());
} else if (cfgObj instanceof Number) {
return ((Number) cfgObj).intValue();
} else {
throw new IllegalArgumentException("Can't parse " + cfgKey + " from the config. Value is " + cfgObj);
}
}
@Override
public void setClientCredentials(KeycloakDeployment deployment, Map<String, String> requestHeaders, Map<String, String> formParams) {
String signedToken = createSignedRequestToken(deployment.getResourceName(), deployment.getRealmInfoUrl());
formParams.put(OAuth2Constants.CLIENT_ASSERTION_TYPE, OAuth2Constants.CLIENT_ASSERTION_TYPE_JWT);
formParams.put(OAuth2Constants.CLIENT_ASSERTION, signedToken);
}
public String createSignedRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken jwt = createRequestToken(clientId, realmInfoUrl);
return new JWSBuilder()
.jsonContent(jwt)
.rsa256(privateKey);
}
protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) {
JsonWebToken reqToken = new JsonWebToken();
reqToken.id(AdapterUtils.generateId());
reqToken.issuer(clientId);
reqToken.subject(clientId);
reqToken.audience(realmInfoUrl);
int now = Time.currentTime();
reqToken.issuedAt(now);
reqToken.expiration(now + this.tokenTimeout);
reqToken.notBefore(now);
return reqToken;
}
}
@@ -1,241 +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.jaas;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.security.Principal;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
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 org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.RSATokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.common.util.FindFile;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import org.keycloak.common.util.reflections.Reflections;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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";
protected Subject subject;
protected CallbackHandler callbackHandler;
protected Auth auth;
protected KeycloakDeployment deployment;
protected String rolePrincipalClass;
// This is to avoid parsing keycloak.json file in each request. Key is file location, Value is parsed keycloak deployment
private static ConcurrentMap<String, KeycloakDeployment> deployments = new ConcurrentHashMap<String, KeycloakDeployment>();
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION);
rolePrincipalClass = (String)options.get(ROLE_PRINCIPAL_CLASS_OPTION);
getLogger().debug("Declared options: " + KEYCLOAK_CONFIG_FILE_OPTION + "=" + configFile + ", " + ROLE_PRINCIPAL_CLASS_OPTION + "=" + rolePrincipalClass);
if (configFile != null) {
deployment = deployments.get(configFile);
if (deployment == null) {
// lazy init of our deployment
deployment = resolveDeployment(configFile);
deployments.putIfAbsent(configFile, deployment);
}
}
}
protected KeycloakDeployment resolveDeployment(String keycloakConfigFile) {
try {
InputStream is = FindFile.findFile(keycloakConfigFile);
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
if (kd.getRealmKey() == null) {
new AdapterDeploymentContext().resolveRealmKey(kd);
}
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 {
// get username and password
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("username");
callbacks[1] = new PasswordCallback("password", false);
try {
callbackHandler.handle(callbacks);
String username = ((NameCallback) callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
String password = new String(tmpPassword);
((PasswordCallback) callbacks[1]).clearPassword();
Auth auth = doAuth(username, password);
if (auth != null) {
this.auth = auth;
return true;
} else {
return false;
}
} catch (UnsupportedCallbackException uce) {
getLogger().warn("Error: " + uce.getCallback().toString()
+ " not available to gather authentication information from the user");
return false;
} catch (Exception e) {
LoginException le = new LoginException(e.toString());
le.initCause(e);
throw le;
}
}
@Override
public boolean commit() throws LoginException {
if (auth == null) {
return false;
}
this.subject.getPrincipals().add(auth.getPrincipal());
this.subject.getPrivateCredentials().add(auth.getTokenString());
if (auth.getRoles() != null) {
for (String roleName : auth.getRoles()) {
Principal rolePrinc = createRolePrincipal(roleName);
this.subject.getPrincipals().add(rolePrinc);
}
}
return true;
}
protected Principal createRolePrincipal(String roleName) {
if (rolePrincipalClass != null && rolePrincipalClass.length() > 0) {
try {
Class<Principal> clazz = Reflections.classForName(rolePrincipalClass, getClass().getClassLoader());
Constructor<Principal> constructor = clazz.getDeclaredConstructor(String.class);
return constructor.newInstance(roleName);
} catch (Exception e) {
getLogger().warn("Unable to create declared roleClass " + rolePrincipalClass + " due to " + e.getMessage());
}
}
// Fallback to default rolePrincipal class
return new RolePrincipal(roleName);
}
@Override
public boolean abort() throws LoginException {
return true;
}
@Override
public boolean logout() throws LoginException {
Set<Principal> principals = new HashSet<Principal>(subject.getPrincipals());
for (Principal principal : principals) {
if (principal.getClass().equals(KeycloakPrincipal.class) || principal.getClass().equals(RolePrincipal.class)) {
subject.getPrincipals().remove(principal);
}
}
Set<Object> creds = subject.getPrivateCredentials();
for (Object cred : creds) {
subject.getPrivateCredentials().remove(cred);
}
subject = null;
callbackHandler = null;
return true;
}
protected Auth bearerAuth(String tokenString) throws VerificationException {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, deployment.getRealmKey(), deployment.getRealmInfoUrl());
boolean verifyCaller;
if (deployment.isUseResourceRoleMappings()) {
verifyCaller = token.isVerifyCaller(deployment.getResourceName());
} else {
verifyCaller = token.isVerifyCaller();
}
if (verifyCaller) {
throw new IllegalStateException("VerifyCaller not supported yet in login module");
}
RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(deployment, null, tokenString, token, null, null, null);
String principalName = AdapterUtils.getPrincipalName(deployment, token);
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(principalName, skSession);
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(skSession);
return new Auth(principal, roles, tokenString);
}
protected abstract Auth doAuth(String username, String password) throws Exception;
protected abstract Logger getLogger();
public static class Auth {
private final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
private final Set<String> roles;
private final String tokenString;
public Auth(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, Set<String> roles, String accessToken) {
this.principal = principal;
this.roles = roles;
this.tokenString = accessToken;
}
public KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipal() {
return principal;
}
public Set<String> getRoles() {
return roles;
}
public String getTokenString() {
return tokenString;
}
}
}
@@ -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.jaas;
import org.jboss.logging.Logger;
import org.keycloak.common.VerificationException;
/**
* Login module, which allows to authenticate Keycloak access token in environments, which rely on JAAS
* <p/>
* It expects login based on username and password where username doesn't matter and password is keycloak access token.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class BearerTokenLoginModule extends AbstractKeycloakLoginModule {
private static final Logger log = Logger.getLogger(BearerTokenLoginModule.class);
@Override
protected Auth doAuth(String username, String password) throws VerificationException {
// Should do some checking of authenticated username if it's equivalent to passed value?
return bearerAuth(password);
}
@Override
protected Logger getLogger() {
return log;
}
}
@@ -1,187 +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.jaas;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.VerificationException;
import org.keycloak.adapters.authentication.ClientCredentialsProviderUtils;
import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.util.JsonSerialization;
import org.keycloak.common.util.KeycloakUriBuilder;
/**
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
* can't rely on HTTP (like SSH authentication for instance). It needs that Direct Grant is enabled on particular realm in Keycloak.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule {
private static final Logger log = Logger.getLogger(DirectAccessGrantsLoginModule.class);
private String refreshToken;
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
super.initialize(subject, callbackHandler, sharedState, options);
// This is used just for logout
Iterator<RefreshTokenHolder> iterator = subject.getPrivateCredentials(RefreshTokenHolder.class).iterator();
if (iterator.hasNext()) {
refreshToken = iterator.next().refreshToken;
}
}
@Override
protected Auth doAuth(String username, String password) throws IOException, VerificationException {
return directGrantAuth(username, password);
}
@Override
protected Logger getLogger() {
return log;
}
protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException {
String authServerBaseUrl = deployment.getAuthServerBaseUrl();
URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm());
HttpPost post = new HttpPost(directGrantUri);
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD));
formparams.add(new BasicNameValuePair("username", username));
formparams.add(new BasicNameValuePair("password", password));
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpClient client = deployment.getClient();
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 200) {
StringBuilder errorBuilder = new StringBuilder("Login failed. Invalid status: " + status);
if (entity != null) {
InputStream is = entity.getContent();
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
}
String error = errorBuilder.toString();
log.warn(error);
throw new IOException(error);
}
if (entity == null) {
throw new IOException("No Entity");
}
InputStream is = entity.getContent();
AccessTokenResponse tokenResponse = JsonSerialization.readValue(is, AccessTokenResponse.class);
// refreshToken will be saved to privateCreds of Subject for now
refreshToken = tokenResponse.getRefreshToken();
return bearerAuth(tokenResponse.getToken());
}
@Override
public boolean commit() throws LoginException {
boolean superCommit = super.commit();
// refreshToken will be saved to privateCreds of Subject for now
if (refreshToken != null) {
RefreshTokenHolder refreshTokenHolder = new RefreshTokenHolder();
refreshTokenHolder.refreshToken = refreshToken;
subject.getPrivateCredentials().add(refreshTokenHolder);
}
return superCommit;
}
@Override
public boolean logout() throws LoginException {
if (refreshToken != null) {
try {
URI logoutUri = deployment.getLogoutUrl().clone().build();
HttpPost post = new HttpPost(logoutUri);
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
ClientCredentialsProviderUtils.setClientCredentials(deployment, post, formparams);
formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken));
UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
post.setEntity(form);
HttpClient client = deployment.getClient();
HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
if (status != 204) {
StringBuilder errorBuilder = new StringBuilder("Logout of refreshToken failed. Invalid status: " + status);
if (entity != null) {
InputStream is = entity.getContent();
if (status == 400) {
Map<String, String> errors = (Map<String, String>) JsonSerialization.readValue(is, Map.class);
errorBuilder.append(", OAuth2 error. Error: " + errors.get(OAuth2Constants.ERROR))
.append(", Error description: " + errors.get(OAuth2Constants.ERROR_DESCRIPTION));
} else {
if (is != null) is.close();
}
}
// Should do something better than warn if logout failed? Perhaps update of refresh tokens on existing subject might be supported too...
log.warn(errorBuilder.toString());
}
} catch (IOException ioe) {
log.warn(ioe);
}
}
return super.logout();
}
private static class RefreshTokenHolder implements Serializable {
private String refreshToken;
}
}
@@ -1,53 +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.jaas;
import java.io.Serializable;
import java.security.Principal;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class RolePrincipal implements Principal, Serializable {
private String roleName = null;
public RolePrincipal(String roleName) {
this.roleName = roleName;
}
public boolean equals (Object p) {
if (! (p instanceof RolePrincipal))
return false;
return getName().equals(((RolePrincipal)p).getName());
}
public int hashCode () {
return getName().hashCode();
}
public String getName () {
return this.roleName;
}
public String toString ()
{
return getName();
}
}
@@ -1,19 +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.
#
org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider
org.keycloak.adapters.authentication.JWTClientCredentialsProvider
@@ -1,78 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.junit.Test;
import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider;
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
import org.keycloak.common.enums.RelativeUrlsUsed;
import org.keycloak.common.enums.SslRequired;
import org.keycloak.enums.TokenStore;
import org.keycloak.common.util.PemUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class KeycloakDeploymentBuilderTest {
@Test
public void load() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak.json"));
assertEquals("demo", deployment.getRealm());
assertEquals("customer-portal", deployment.getResourceName());
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey());
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired());
assertTrue(deployment.isUseResourceRoleMappings());
assertTrue(deployment.isCors());
assertEquals(1000, deployment.getCorsMaxAge());
assertEquals("POST, PUT, DELETE, GET", deployment.getCorsAllowedMethods());
assertEquals("X-Custom, X-Custom2", deployment.getCorsAllowedHeaders());
assertTrue(deployment.isBearerOnly());
assertTrue(deployment.isPublicClient());
assertTrue(deployment.isEnableBasicAuth());
assertTrue(deployment.isExposeToken());
assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal());
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl());
assertEquals(RelativeUrlsUsed.NEVER, deployment.getRelativeUrls());
assertTrue(deployment.isAlwaysRefreshToken());
assertTrue(deployment.isRegisterNodeAtStartup());
assertEquals(1000, deployment.getRegisterNodePeriod());
assertEquals(TokenStore.COOKIE, deployment.getTokenStore());
assertEquals("email", deployment.getPrincipalAttribute());
}
@Test
public void loadNoClientCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
@Test
public void loadJwtCredentials() throws Exception {
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-jwt.json"));
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
}
}
@@ -1,13 +0,0 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"ssl-required": "external",
"credentials": {
"jwt": {
"client-keystore-file": "classpath:keystore.jks",
"client-keystore-password": "storepass"
}
}
}
@@ -1,8 +0,0 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"public-client": true,
"expose-token": true
}
@@ -1,32 +0,0 @@
{
"realm": "demo",
"resource": "customer-portal",
"realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url": "https://localhost:8443/auth",
"ssl-required": "external",
"use-resource-role-mappings": true,
"enable-cors": true,
"cors-max-age": 1000,
"cors-allowed-methods": "POST, PUT, DELETE, GET",
"cors-allowed-headers": "X-Custom, X-Custom2",
"bearer-only": true,
"public-client": true,
"enable-basic-auth": true,
"expose-token": true,
"credentials": {
"secret": "234234-234234-234234"
},
"connection-pool-size": 20,
"disable-trust-manager": true,
"allow-any-hostname": true,
"truststore": "classpath:/cacerts.jks",
"truststore-password": "changeit",
"client-keystore": "classpath:/keystore.jks",
"client-keystore-password": "storepass",
"client-key-password": "keypass",
"always-refresh-token": true,
"register-node-at-startup": true,
"register-node-period": 1000,
"token-store": "cookie",
"principal-attribute": "email"
}
@@ -1,102 +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-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>1.9.7.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-as7-adapter-spi</artifactId>
<name>Keycloak AS7 SPI</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.web</groupId>
<artifactId>jbossweb</artifactId>
<version>7.0.17.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-web</artifactId>
<version>7.1.2.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat-adapter-spi</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,185 +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.jbossweb;
import org.apache.catalina.Realm;
import org.apache.catalina.realm.GenericPrincipal;
import org.jboss.as.web.security.JBossGenericPrincipal;
import org.jboss.security.NestableGroup;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.keycloak.adapters.spi.KeycloakAccount;
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
import javax.security.auth.Subject;
import java.lang.reflect.Constructor;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JBossWebPrincipalFactory extends GenericPrincipalFactory {
private static Constructor jbossWebPrincipalConstructor = findJBossGenericPrincipalConstructor();
@Override
protected GenericPrincipal createPrincipal(Principal userPrincipal, List<String> roles) {
return null;
}
@Override
public GenericPrincipal createPrincipal(Realm realm, final Principal identity, final Set<String> roleSet) {
KeycloakAccount account = new KeycloakAccount() {
@Override
public Principal getPrincipal() {
return identity;
}
@Override
public Set<String> getRoles() {
return roleSet;
}
};
Subject subject = new Subject();
Set<Principal> principals = subject.getPrincipals();
principals.add(identity);
Group[] roleSets = getRoleSets(roleSet);
for (int g = 0; g < roleSets.length; g++) {
Group group = roleSets[g];
String name = group.getName();
Group subjectGroup = createGroup(name, principals);
if (subjectGroup instanceof NestableGroup) {
/* A NestableGroup only allows Groups to be added to it so we
need to add a SimpleGroup to subjectRoles to contain the roles
*/
SimpleGroup tmp = new SimpleGroup("Roles");
subjectGroup.addMember(tmp);
subjectGroup = tmp;
}
// Copy the group members to the Subject group
Enumeration<? extends Principal> members = group.members();
while (members.hasMoreElements()) {
Principal role = (Principal) members.nextElement();
subjectGroup.addMember(role);
}
}
// add the CallerPrincipal group if none has been added in getRoleSets
Group callerGroup = new SimpleGroup(SecurityConstants.CALLER_PRINCIPAL_GROUP);
callerGroup.addMember(identity);
principals.add(callerGroup);
SecurityContext sc = SecurityContextAssociation.getSecurityContext();
Principal userPrincipal = getPrincipal(subject);
sc.getUtil().createSubjectInfo(userPrincipal, account, subject);
List<String> rolesAsStringList = new ArrayList<String>();
rolesAsStringList.addAll(roleSet);
try {
return (GenericPrincipal) jbossWebPrincipalConstructor.newInstance(realm, userPrincipal.getName(), null, rolesAsStringList, userPrincipal, null, account, null, subject);
} catch (Throwable t) {
throw new RuntimeException("Failed to create JBossGenericPrincipal", t);
}
}
/**
* Get the Principal given the authenticated Subject. Currently the first subject that is not of type {@code Group} is
* considered or the single subject inside the CallerPrincipal group.
*
* @param subject
* @return the authenticated subject
*/
protected Principal getPrincipal(Subject subject) {
Principal principal = null;
Principal callerPrincipal = null;
if (subject != null) {
Set<Principal> principals = subject.getPrincipals();
if (principals != null && !principals.isEmpty()) {
for (Principal p : principals) {
if (!(p instanceof Group) && principal == null) {
principal = p;
}
if (p instanceof Group) {
Group g = Group.class.cast(p);
if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
Enumeration<? extends Principal> e = g.members();
if (e.hasMoreElements())
callerPrincipal = e.nextElement();
}
}
}
}
}
return callerPrincipal == null ? principal : callerPrincipal;
}
protected Group createGroup(String name, Set<Principal> principals) {
Group roles = null;
Iterator<Principal> iter = principals.iterator();
while (iter.hasNext()) {
Object next = iter.next();
if (!(next instanceof Group))
continue;
Group grp = (Group) next;
if (grp.getName().equals(name)) {
roles = grp;
break;
}
}
// If we did not find a group create one
if (roles == null) {
roles = new SimpleGroup(name);
principals.add(roles);
}
return roles;
}
protected Group[] getRoleSets(Collection<String> roleSet) {
SimpleGroup roles = new SimpleGroup("Roles");
Group[] roleSets = {roles};
for (String role : roleSet) {
roles.addMember(new SimplePrincipal(role));
}
return roleSets;
}
static Constructor findJBossGenericPrincipalConstructor() {
for (Constructor<?> c : JBossGenericPrincipal.class.getConstructors()) {
if (c.getParameterTypes().length == 9 &&
c.getParameterTypes()[0].equals(Realm.class) &&
c.getParameterTypes()[1].equals(String.class) &&
c.getParameterTypes()[3].equals(List.class) &&
c.getParameterTypes()[4].equals(Principal.class) &&
c.getParameterTypes()[6].equals(Object.class) &&
c.getParameterTypes()[8].equals(Subject.class)) {
return c;
}
}
return null;
}
}
@@ -1,46 +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.jbossweb;
import org.apache.catalina.Realm;
import org.junit.Assert;
import org.junit.Test;
import javax.security.auth.Subject;
import java.lang.reflect.Constructor;
import java.security.Principal;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JBossWebPrincipalFactoryTest {
@Test
public void test() {
Constructor constructor = JBossWebPrincipalFactory.findJBossGenericPrincipalConstructor();
Assert.assertNotNull(constructor);
Assert.assertEquals(Realm.class, constructor.getParameterTypes()[0]);
Assert.assertEquals(String.class, constructor.getParameterTypes()[1]);
Assert.assertEquals(List.class, constructor.getParameterTypes()[3]);
Assert.assertEquals(Principal.class, constructor.getParameterTypes()[4]);
Assert.assertEquals(Object.class, constructor.getParameterTypes()[6]);
Assert.assertEquals(Subject.class, constructor.getParameterTypes()[8]);
}
}
-134
View File
@@ -1,134 +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-as7-integration-pom</artifactId>
<groupId>org.keycloak</groupId>
<version>1.9.7.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-as7-adapter</artifactId>
<name>Keycloak AS7 Integration</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jboss-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.web</groupId>
<artifactId>jbossweb</artifactId>
<version>7.0.17.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-web</artifactId>
<version>7.1.2.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-tomcat-core-adapter</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jbossweb;
import org.apache.catalina.LifecycleException;
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.tomcat.AbstractKeycloakAuthenticatorValve;
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.List;
/**
* Keycloak authentication valve
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakAuthenticatorValve extends AbstractKeycloakAuthenticatorValve {
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws java.io.IOException {
return authenticateInternal(request, response, config);
}
@Override
protected boolean forwardToErrorPageInternal(Request request, HttpServletResponse response, Object loginConfig) throws IOException {
if (loginConfig == null) return false;
LoginConfig config = (LoginConfig)loginConfig;
if (config.getErrorPage() == null) return false;
forwardToErrorPage(request, (Response)response, config);
return true;
}
@Override
public void start() throws LifecycleException {
StandardContext standardContext = (StandardContext) context;
standardContext.addLifecycleListener(this);
super.start();
}
public void logout(Request request) {
logoutInternal(request);
}
@Override
protected GenericPrincipalFactory createPrincipalFactory() {
return new JBossWebPrincipalFactory();
}
}
@@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-integration-pom</artifactId>
<version>1.9.7.Final</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>keycloak-as7-subsystem</artifactId>
<name>Keycloak AS7 Subsystem</name>
<description/>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.8.1</version>
<configuration>
<redirectTestOutputToFile>false</redirectTestOutputToFile>
<enableAssertions>true</enableAssertions>
<argLine>-Xmx512m</argLine>
<systemProperties>
<property>
<name>jboss.home</name>
<value>${jboss.home}</value>
</property>
</systemProperties>
<includes>
<include>**/*TestCase.java</include>
</includes>
<forkMode>once</forkMode>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-as7-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-naming</artifactId>
<version>${jboss.as.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-server</artifactId>
<version>${jboss.as.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-ee</artifactId>
<version>${jboss.as.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-web</artifactId>
<version>${jboss.as.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
projects that depend on this project.-->
<scope>provided</scope>
<optional>true</optional>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.msc</groupId>
<artifactId>jboss-msc</artifactId>
<version>1.0.2.GA</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,62 +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.subsystem.as7;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.ServiceVerificationHandler;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Add a credential to a deployment.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class CredentialAddHandler extends AbstractAddStepHandler {
protected Collection<AttributeDefinition> attributes = new LinkedList<>();
public CredentialAddHandler(AttributeDefinition... attrs) {
for (AttributeDefinition attr : attrs) {
attributes.add(attr);
}
}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
if (attributes != null) {
for (AttributeDefinition attr : attributes) {
attr.validateAndSet(operation, model);
}
}
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addCredential(operation, context.resolveExpressions(model));
}
}
@@ -1,63 +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.subsystem.as7;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.dmr.ModelType;
/**
* Defines attributes and operations for a credential.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public class CredentialDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "credential";
protected static final AttributeDefinition VALUE =
new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
.build();
public CredentialDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
new CredentialAddHandler(VALUE),
CredentialRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(ModelDescriptionConstants.DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler());
}
}
@@ -1,50 +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.subsystem.as7;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
/**
* Update a credential value.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.updateCredential(operation, attributeName, resolvedValue);
hh.setHandback(ckService);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateCredential(operation, attributeName, valueToRestore);
}
}
@@ -1,42 +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.subsystem.as7;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
/**
* Remove a credential from a deployment.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public final class CredentialRemoveHandler extends AbstractRemoveStepHandler {
public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler();
private CredentialRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeCredential(operation);
}
}
@@ -1,129 +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.subsystem.as7;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.web.deployment.WarMetaData;
import org.jboss.logging.Logger;
import org.jboss.metadata.javaee.spec.ParamValueMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.jboss.ValveMetaData;
import org.jboss.metadata.web.spec.LoginConfigMetaData;
import org.keycloak.adapters.jbossweb.KeycloakAuthenticatorValve;
import org.keycloak.subsystem.as7.logging.KeycloakLogger;
import java.util.ArrayList;
import java.util.List;
/**
* Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
*/
public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class);
// This param name is defined again in Keycloak Undertow Integration class
// org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in
// two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
String deploymentName = deploymentUnit.getName();
// if it's not a web-app there's nothing to secure
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) {
return;
}
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
webMetaData = new JBossWebMetaData();
warMetaData.setMergedJBossWebMetaData(webMetaData);
}
KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
// otherwise
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
boolean hasSubsystemConfig = service.isSecureDeployment(deploymentName);
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
if (hasSubsystemConfig || webRequiresKC) {
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentName);
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
if (hasSubsystemConfig) {
addJSONData(service.getJSON(deploymentName), warMetaData);
if (loginConfig != null) {
loginConfig.setAuthMethod("KEYCLOAK");
loginConfig.setRealmName(service.getRealmName(deploymentName));
} else {
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentName + " (loginConfig == null)");
}
}
addValve(webMetaData);
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
}
}
private void addValve(JBossWebMetaData webMetaData) {
List<ValveMetaData> valves = webMetaData.getValves();
if (valves == null) {
valves = new ArrayList<ValveMetaData>(1);
webMetaData.setValves(valves);
}
ValveMetaData valve = new ValveMetaData();
valve.setValveClass(KeycloakAuthenticatorValve.class.getName());
valve.setModule("org.keycloak.keycloak-as7-adapter");
//log.info("******* adding Keycloak valve to: " + deploymentName);
valves.add(valve);
}
private void addJSONData(String json, WarMetaData warMetaData) {
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
webMetaData = new JBossWebMetaData();
warMetaData.setMergedJBossWebMetaData(webMetaData);
}
List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
if (contextParams == null) {
contextParams = new ArrayList<ParamValueMetaData>();
}
ParamValueMetaData param = new ParamValueMetaData();
param.setParamName(AUTH_DATA_PARAM_NAME);
param.setParamValue(json);
contextParams.add(param);
webMetaData.setContextParams(contextParams);
}
@Override
public void undeploy(DeploymentUnit du) {
}
}
@@ -1,192 +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.subsystem.as7;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.logging.Logger;
import java.util.HashMap;
import java.util.Map;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
/**
* This service keeps track of the entire Keycloak management model so as to provide
* adapter configuration to each deployment at deploy time.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public final class KeycloakAdapterConfigService {
protected Logger log = Logger.getLogger(KeycloakAdapterConfigService.class);
private static final String CREDENTIALS_JSON_NAME = "credentials";
private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
public static KeycloakAdapterConfigService getInstance() {
return INSTANCE;
}
private final Map<String, ModelNode> realms = new HashMap<String, ModelNode>();
// keycloak-secured deployments
private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
private KeycloakAdapterConfigService() {
}
public void addRealm(ModelNode operation, ModelNode model) {
this.realms.put(realmNameFromOp(operation), model.clone());
}
public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode realm = this.realms.get(realmNameFromOp(operation));
realm.get(attrName).set(resolvedValue);
}
public void removeRealm(ModelNode operation) {
this.realms.remove(realmNameFromOp(operation));
}
public void addSecureDeployment(ModelNode operation, ModelNode model) {
ModelNode deployment = model.clone();
this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
}
public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(attrName).set(resolvedValue);
}
public void removeSecureDeployment(ModelNode operation) {
this.secureDeployments.remove(deploymentNameFromOp(operation));
}
public void addCredential(ModelNode operation, ModelNode model) {
ModelNode credentials = credentialsFromOp(operation);
if (!credentials.isDefined()) {
credentials = new ModelNode();
}
String credentialName = credentialNameFromOp(operation);
if (!credentialName.contains(".")) {
credentials.get(credentialName).set(model.get("value").asString());
} else {
String[] parts = credentialName.split("\\.");
String provider = parts[0];
String property = parts[1];
ModelNode credential = credentials.get(provider);
if (!credential.isDefined()) {
credential = new ModelNode();
}
credential.get(property).set(model.get("value").asString());
credentials.set(provider, credential);
}
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
}
public void removeCredential(ModelNode operation) {
ModelNode credentials = credentialsFromOp(operation);
if (!credentials.isDefined()) {
throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString());
}
String credentialName = credentialNameFromOp(operation);
credentials.remove(credentialName);
}
public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) {
ModelNode credentials = credentialsFromOp(operation);
if (!credentials.isDefined()) {
throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString());
}
String credentialName = credentialNameFromOp(operation);
credentials.get(credentialName).set(resolvedValue);
}
private ModelNode credentialsFromOp(ModelNode operation) {
ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
return deployment.get(CREDENTIALS_JSON_NAME);
}
private String realmNameFromOp(ModelNode operation) {
return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
}
private String deploymentNameFromOp(ModelNode operation) {
return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
}
private String credentialNameFromOp(ModelNode operation) {
return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
}
private String valueFromOpAddress(String addrElement, ModelNode operation) {
String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());
return deploymentName;
}
private String getValueOfAddrElement(ModelNode address, String elementName) {
for (ModelNode element : address.asList()) {
if (element.has(elementName)) return element.get(elementName).asString();
}
return null;
}
public String getRealmName(String deploymentName) {
ModelNode deployment = this.secureDeployments.get(deploymentName);
return deployment.get(RealmDefinition.TAG_NAME).asString();
}
public String getJSON(String deploymentName) {
ModelNode deployment = this.secureDeployments.get(deploymentName);
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
ModelNode realm = this.realms.get(realmName);
ModelNode json = new ModelNode();
json.get(RealmDefinition.TAG_NAME).set(realmName);
// Realm values set first. Some can be overridden by deployment values.
if (realm != null) setJSONValues(json, realm);
setJSONValues(json, deployment);
return json.toJSONString(true);
}
private void setJSONValues(ModelNode json, ModelNode values) {
for (Property prop : values.asPropertyList()) {
String name = prop.getName();
ModelNode value = prop.getValue();
if (value.isDefined()) {
json.get(name).set(value);
}
}
}
public boolean isSecureDeployment(String deploymentName) {
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
return this.secureDeployments.containsKey(deploymentName);
}
}
@@ -1,86 +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.subsystem.as7;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.as.web.deployment.WarMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.LoginConfigMetaData;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
String deploymentName = deploymentUnit.getName();
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentName)) {
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
if (warMetaData == null) {
return;
}
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
if (webMetaData == null) {
return;
}
LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
if (loginConfig == null) return;
if (loginConfig.getAuthMethod() == null) return;
if (!loginConfig.getAuthMethod().equals("KEYCLOAK")) return;
}
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
addCommonModules(moduleSpecification, moduleLoader);
addPlatformSpecificModules(moduleSpecification, moduleLoader);
}
private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
}
abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
@Override
public void undeploy(DeploymentUnit du) {
}
}
@@ -1,36 +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.subsystem.as7;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
/**
* @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>
*/
public class KeycloakDependencyProcessorAS7 extends KeycloakDependencyProcessor {
private static final ModuleIdentifier KEYCLOAK_AS7_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-as7-adapter");
@Override
protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
// ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_AS7_ADAPTER, false, false, true, false));
}}
@@ -1,84 +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.subsystem.as7;
import org.jboss.as.controller.Extension;
import org.jboss.as.controller.ExtensionContext;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.SubsystemRegistration;
import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
import org.jboss.as.controller.parsing.ExtensionParsingContext;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.keycloak.subsystem.as7.logging.KeycloakLogger;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
/**
* Main Extension class for the subsystem.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public class KeycloakExtension implements Extension {
public static final String SUBSYSTEM_NAME = "keycloak";
public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.1";
private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions";
private static final int MGMT_API_VERSION_MAJOR = 1;
private static final int MGMT_API_VERSION_MINOR = 1;
static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
for (String kp : keyPrefix) {
prefix.append('.').append(kp);
}
return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false);
}
/**
* {@inheritDoc}
*/
@Override
public void initializeParsers(final ExtensionParsingContext context) {
context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER);
}
/**
* {@inheritDoc}
*/
@Override
public void initialize(final ExtensionContext context) {
KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension");
final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION_MAJOR, MGMT_API_VERSION_MINOR);
ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
registration.registerSubModel(REALM_DEFINITION);
ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
subsystem.registerXMLElementWriter(PARSER);
}
}
@@ -1,60 +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.subsystem.as7;
import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.ServiceVerificationHandler;
import org.jboss.as.server.AbstractDeploymentChainStep;
import org.jboss.as.server.DeploymentProcessorTarget;
import org.jboss.as.server.deployment.Phase;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import java.util.List;
/**
* The Keycloak subsystem add update handler.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
@Override
protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) {
context.addStep(new AbstractDeploymentChainStep() {
@Override
protected void execute(DeploymentProcessorTarget processorTarget) {
processorTarget.addDeploymentProcessor(Phase.DEPENDENCIES, 0, new KeycloakDependencyProcessorAS7());
processorTarget.addDeploymentProcessor(
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
new KeycloakAdapterConfigDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
model.setEmptyObject();
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.as7;
import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
/**
* Definition of subsystem=keycloak.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
protected KeycloakSubsystemDefinition() {
super(KeycloakExtension.SUBSYSTEM_PATH,
KeycloakExtension.getResourceDescriptionResolver("subsystem"),
KeycloakSubsystemAdd.INSTANCE,
ReloadRequiredRemoveStepHandler.INSTANCE
);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(ModelDescriptionConstants.DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
}
}
@@ -1,294 +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.subsystem.as7;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLElementReader;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* The subsystem parser, which uses stax to read and write to and from xml
*/
class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<List<ModelNode>>, XMLElementWriter<SubsystemMarshallingContext> {
/**
* {@inheritDoc}
*/
@Override
public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> list) throws XMLStreamException {
// Require no attributes
ParseUtils.requireNoAttributes(reader);
ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM));
list.add(addKeycloakSub);
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
if (reader.getLocalName().equals(RealmDefinition.TAG_NAME)) {
readRealm(reader, list);
}
else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
readDeployment(reader, list);
}
}
}
// used for debugging
private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException {
return reader.nextTag();
}
private void readRealm(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
String realmName = readNameAttribute(reader);
ModelNode addRealm = new ModelNode();
addRealm.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(RealmDefinition.TAG_NAME, realmName));
addRealm.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName();
SimpleAttributeDefinition def = RealmDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName);
def.parseAndSetParameter(reader.getElementText(), addRealm, reader);
}
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
list.add(addRealm);
}
private void readDeployment(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
ModelNode addSecureDeployment = new ModelNode();
addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
String tagName = reader.getLocalName();
if (tagName.equals(CredentialDefinition.TAG_NAME)) {
readCredential(reader, addr, credentialsToAdd);
continue;
}
SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader);
}
/**
* TODO need to check realm-ref first.
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
//TODO: externalize the message
throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
*/
// Must add credentials after the deployment is added.
resourcesToAdd.add(addSecureDeployment);
resourcesToAdd.addAll(credentialsToAdd);
}
public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
String name = readNameAttribute(reader);
Map<String, String> values = new HashMap<>();
String textValue = null;
while (reader.hasNext()) {
int next = reader.next();
if (next == CHARACTERS) {
// text value of credential element (like for "secret" )
String text = reader.getText();
if (text == null || text.trim().isEmpty()) {
continue;
}
textValue = text;
} else if (next == START_ELEMENT) {
String key = reader.getLocalName();
reader.next();
String value = reader.getText();
reader.next();
values.put(key, value);
} else if (next == END_ELEMENT) {
break;
}
}
if (textValue != null) {
ModelNode addCredential = getCredentialToAdd(parent, name, textValue);
credentialsToAdd.add(addCredential);
} else {
for (Map.Entry<String, String> entry : values.entrySet()) {
ModelNode addCredential = getCredentialToAdd(parent, name + "" + entry.getKey(), entry.getValue());
credentialsToAdd.add(addCredential);
}
}
}
private ModelNode getCredentialToAdd(PathAddress parent, String name, String value) {
ModelNode addCredential = new ModelNode();
addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
addCredential.get(CredentialDefinition.VALUE.getName()).set(value);
return addCredential;
}
// expects that the current tag will have one single attribute called "name"
private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
String name = null;
for (int i = 0; i < reader.getAttributeCount(); i++) {
String attr = reader.getAttributeLocalName(i);
if (attr.equals("name")) {
name = reader.getAttributeValue(i);
continue;
}
throw ParseUtils.unexpectedAttribute(reader, i);
}
if (name == null) {
throw ParseUtils.missingRequired(reader, Collections.singleton("name"));
}
return name;
}
/**
* {@inheritDoc}
*/
@Override
public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
writeRealms(writer, context);
writeSecureDeployments(writer, context);
writer.writeEndElement();
}
private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) {
return;
}
for (Property realm : context.getModelNode().get(RealmDefinition.TAG_NAME).asPropertyList()) {
writer.writeStartElement(RealmDefinition.TAG_NAME);
writer.writeAttribute("name", realm.getName());
ModelNode realmElements = realm.getValue();
for (AttributeDefinition element : RealmDefinition.ALL_ATTRIBUTES) {
element.marshallAsElement(realmElements, writer);
}
writer.writeEndElement();
}
}
private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
if (!context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).isDefined()) {
return;
}
for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).asPropertyList()) {
writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME);
writer.writeAttribute("name", deployment.getName());
ModelNode deploymentElements = deployment.getValue();
for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
element.marshallAsElement(deploymentElements, writer);
}
ModelNode credentials = deploymentElements.get(CredentialDefinition.TAG_NAME);
if (credentials.isDefined()) {
writeCredentials(writer, credentials);
}
writer.writeEndElement();
}
}
private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
Map<String, Object> parsed = new LinkedHashMap<>();
for (Property credential : credentials.asPropertyList()) {
String credName = credential.getName();
String credValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
if (credName.indexOf('.') > -1) {
String[] parts = credName.split("\\.");
String provider = parts[0];
String propKey = parts[1];
Map<String, String> currentProviderMap = (Map<String, String>) parsed.get(provider);
if (currentProviderMap == null) {
currentProviderMap = new LinkedHashMap<>();
parsed.put(provider, currentProviderMap);
}
currentProviderMap.put(propKey, credValue);
} else {
parsed.put(credName, credValue);
}
}
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
writer.writeStartElement(CredentialDefinition.TAG_NAME);
writer.writeAttribute("name", entry.getKey());
Object value = entry.getValue();
if (value instanceof String) {
writeCharacters(writer, (String) value);
} else {
Map<String, String> credentialProps = (Map<String, String>) value;
for (Map.Entry<String, String> prop : credentialProps.entrySet()) {
writer.writeStartElement(prop.getKey());
writeCharacters(writer, prop.getValue());
writer.writeEndElement();
}
}
writer.writeEndElement();
}
}
// code taken from org.jboss.as.controller.AttributeMarshaller
private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
if (content.indexOf('\n') > -1) {
// Multiline content. Use the overloaded variant that staxmapper will format
writer.writeCharacters(content);
} else {
// Staxmapper will just output the chars without adding newlines if this is used
char[] chars = content.toCharArray();
writer.writeCharacters(chars, 0, chars.length);
}
}
}
@@ -1,58 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.as7;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.ServiceVerificationHandler;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import java.util.List;
/**
* Add a new realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public final class RealmAddHandler extends AbstractAddStepHandler {
public static RealmAddHandler INSTANCE = new RealmAddHandler();
private RealmAddHandler() {}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) {
attrib.validateAndSet(operation, model);
}
if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
//TODO: externalize message
throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
}
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addRealm(operation, context.resolveExpressions(model));
}
}
@@ -1,89 +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.subsystem.as7;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Defines attributes and operations for the Realm
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class RealmDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "realm";
protected static final List<SimpleAttributeDefinition> REALM_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
}
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES);
ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
}
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
static {
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
DEFINITION_LOOKUP.put(def.getXmlName(), def);
}
}
private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0]));
public RealmDefinition() {
super(PathElement.pathElement("realm"),
KeycloakExtension.getResourceDescriptionResolver("realm"),
RealmAddHandler.INSTANCE,
RealmRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(ModelDescriptionConstants.DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
//TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired
resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler);
}
}
public static SimpleAttributeDefinition lookup(String name) {
return DEFINITION_LOOKUP.get(name);
}
}
@@ -1,41 +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.subsystem.as7;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
/**
* Remove a realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
final class RealmRemoveHandler extends AbstractRemoveStepHandler {
public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler();
private RealmRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeRealm(operation);
}
}
@@ -1,56 +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.subsystem.as7;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
import java.util.List;
/**
* Update an attribute on a realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
public RealmWriteAttributeHandler(AttributeDefinition... definitions) {
super(definitions);
}
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.updateRealm(operation, attributeName, resolvedValue);
hh.setHandback(ckService);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateRealm(operation, attributeName, valueToRestore);
}
}
@@ -1,54 +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.subsystem.as7;
import org.jboss.as.controller.AbstractAddStepHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.ServiceVerificationHandler;
import org.jboss.dmr.ModelNode;
import org.jboss.msc.service.ServiceController;
import java.util.List;
/**
* Add a deployment to a realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
final class SecureDeploymentAddHandler extends AbstractAddStepHandler {
public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
private SecureDeploymentAddHandler() {
}
@Override
protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
attr.validateAndSet(operation, model);
}
}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.addSecureDeployment(operation, context.resolveExpressions(model));
}
}
@@ -1,132 +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.subsystem.as7;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Defines attributes and operations for a secure-deployment.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class SecureDeploymentDefinition extends SimpleResourceDefinition {
public static final String TAG_NAME = "secure-deployment";
protected static final SimpleAttributeDefinition REALM =
new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
.setXmlName("realm")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition RESOURCE =
new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
.setXmlName("resource")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
.setXmlName("use-resource-role-mappings")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition BEARER_ONLY =
new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
.setXmlName("bearer-only")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
.setXmlName("enable-basic-auth")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
.setXmlName("public-client")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
}
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
}
private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
static {
for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
DEFINITION_LOOKUP.put(def.getXmlName(), def);
}
}
private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES);
public SecureDeploymentDefinition() {
super(PathElement.pathElement(TAG_NAME),
KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
SecureDeploymentAddHandler.INSTANCE,
SecureDeploymentRemoveHandler.INSTANCE);
}
@Override
public void registerOperations(ManagementResourceRegistration resourceRegistration) {
super.registerOperations(resourceRegistration);
resourceRegistration.registerOperationHandler(ModelDescriptionConstants.DESCRIBE, GenericSubsystemDescribeHandler.INSTANCE, GenericSubsystemDescribeHandler.INSTANCE, false, OperationEntry.EntryType.PRIVATE);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
super.registerAttributes(resourceRegistration);
for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
}
}
public static SimpleAttributeDefinition lookup(String name) {
return DEFINITION_LOOKUP.get(name);
}
}
@@ -1,41 +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.subsystem.as7;
import org.jboss.as.controller.AbstractRemoveStepHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.dmr.ModelNode;
/**
* Remove a secure-deployment from a realm.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler {
public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler();
private SecureDeploymentRemoveHandler() {}
@Override
protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
ckService.removeSecureDeployment(operation);
}
}
@@ -1,59 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.as7;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.dmr.ModelNode;
import java.util.List;
/**
* Update an attribute on a secure-deployment.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
public SecureDeploymentWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
this(definitions.toArray(new AttributeDefinition[definitions.size()]));
}
public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) {
super(definitions);
}
@Override
protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
hh.setHandback(ckService);
ckService.updateSecureDeployment(operation, attributeName, resolvedValue);
return false;
}
@Override
protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
ckService.updateSecureDeployment(operation, attributeName, valueToRestore);
}
}
@@ -1,227 +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.subsystem.as7;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.operations.validation.IntRangeValidator;
import org.jboss.as.controller.operations.validation.StringLengthValidator;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import java.util.ArrayList;
import java.util.List;
/**
* Defines attributes that can be present in both a realm and an application (secure-deployment).
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
class SharedAttributeDefinitons {
protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY =
new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, true)
.setXmlName("realm-public-key")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition AUTH_SERVER_URL =
new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, true)
.setXmlName("auth-server-url")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition SSL_REQUIRED =
new SimpleAttributeDefinitionBuilder("ssl-required", ModelType.STRING, true)
.setXmlName("ssl-required")
.setAllowExpression(true)
.setDefaultValue(new ModelNode("external"))
.build();
protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME =
new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true)
.setXmlName("allow-any-hostname")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER =
new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true)
.setXmlName("disable-trust-manager")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition TRUSTSTORE =
new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true)
.setXmlName("truststore")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD =
new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true)
.setXmlName("truststore-password")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE =
new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true)
.setXmlName("connection-pool-size")
.setAllowExpression(true)
.setValidator(new IntRangeValidator(0, true))
.build();
protected static final SimpleAttributeDefinition ENABLE_CORS =
new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true)
.setXmlName("enable-cors")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition CLIENT_KEYSTORE =
new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true)
.setXmlName("client-keystore")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD =
new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true)
.setXmlName("client-keystore-password")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD =
new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true)
.setXmlName("client-key-password")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_MAX_AGE =
new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true)
.setXmlName("cors-max-age")
.setAllowExpression(true)
.setValidator(new IntRangeValidator(-1, true))
.build();
protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS =
new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true)
.setXmlName("cors-allowed-headers")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS =
new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true)
.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")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
.setXmlName("auth-server-url-for-backend-requests")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
.setXmlName("always-refresh-token")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
.setXmlName("register-node-at-startup")
.setAllowExpression(true)
.setDefaultValue(new ModelNode(false))
.build();
protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
.setXmlName("register-node-period")
.setAllowExpression(true)
.setValidator(new IntRangeValidator(-1, true))
.build();
protected static final SimpleAttributeDefinition TOKEN_STORE =
new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
.setXmlName("token-store")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
.setXmlName("principal-attribute")
.setAllowExpression(true)
.setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
.build();
protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
static {
ATTRIBUTES.add(REALM_PUBLIC_KEY);
ATTRIBUTES.add(AUTH_SERVER_URL);
ATTRIBUTES.add(TRUSTSTORE);
ATTRIBUTES.add(TRUSTSTORE_PASSWORD);
ATTRIBUTES.add(SSL_REQUIRED);
ATTRIBUTES.add(ALLOW_ANY_HOSTNAME);
ATTRIBUTES.add(DISABLE_TRUST_MANAGER);
ATTRIBUTES.add(CONNECTION_POOL_SIZE);
ATTRIBUTES.add(ENABLE_CORS);
ATTRIBUTES.add(CLIENT_KEYSTORE);
ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD);
ATTRIBUTES.add(CLIENT_KEY_PASSWORD);
ATTRIBUTES.add(CORS_MAX_AGE);
ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
ATTRIBUTES.add(CORS_ALLOWED_METHODS);
ATTRIBUTES.add(EXPOSE_TOKEN);
ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
ATTRIBUTES.add(REGISTER_NODE_PERIOD);
ATTRIBUTES.add(TOKEN_STORE);
ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
}
/**
* truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.
*
* @param attributes The full set of attributes.
*
* @return <code>true</code> if the attributes are valid, <code>false</code> otherwise.
*/
public static boolean validateTruststoreSetIfRequired(ModelNode attributes) {
if (isSet(attributes, DISABLE_TRUST_MANAGER)) {
return true;
}
if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) {
return true;
}
//TODO, look into alternatives & requires properties on AttributeDefinition
return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD);
}
private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
ModelNode attribute = attributes.get(def.getName());
if (def.getType() == ModelType.BOOLEAN) {
return attribute.isDefined() && attribute.asBoolean();
}
return attribute.isDefined() && !attribute.asString().isEmpty();
}
}
@@ -1,59 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.subsystem.as7;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.dmr.ModelNode;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Util {
public static ModelNode createAddOperation(final PathAddress address) {
return createOperation(ModelDescriptionConstants.ADD, address);
}
public static ModelNode createAddOperation() {
return createEmptyOperation(ModelDescriptionConstants.ADD, null);
}
public static ModelNode createRemoveOperation(final PathAddress address) {
return createOperation(ModelDescriptionConstants.REMOVE, address);
}
public static ModelNode createOperation(final String operationName, final PathAddress address) {
return createEmptyOperation(operationName, address);
}
public static ModelNode createEmptyOperation(String operationName, final PathAddress address) {
ModelNode op = new ModelNode();
op.get(OP).set(operationName);
if (address != null) {
op.get(OP_ADDR).set(address.toModelNode());
} else {
// Just establish the standard structure; caller can fill in address later
op.get(OP_ADDR);
}
return op;
}
}
@@ -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.subsystem.as7.logging;
import org.jboss.logging.BasicLogger;
import org.jboss.logging.LogMessage;
import org.jboss.logging.Logger;
import org.jboss.logging.Message;
import org.jboss.logging.MessageLogger;
import static org.jboss.logging.Logger.Level.DEBUG;
import static org.jboss.logging.Logger.Level.INFO;
/**
* This interface to be fleshed out later when error messages are fully externalized.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
@MessageLogger(projectCode = "KEYCLOAK")
public interface KeycloakLogger extends BasicLogger {
/**
* A logger with a category of the package name.
*/
KeycloakLogger ROOT_LOGGER = Logger.getMessageLogger(KeycloakLogger.class, "org.jboss.keycloak");
@LogMessage(level = INFO)
@Message(value = "Keycloak subsystem override for deployment %s")
void deploymentSecured(String deployment);
@LogMessage(level = DEBUG)
@Message(value = "Keycloak has overriden and secured deployment %s")
void warSecured(String deployment);
}
@@ -1,34 +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.subsystem.as7.logging;
import org.jboss.logging.MessageBundle;
import org.jboss.logging.Messages;
/**
* This interface to be fleshed out later when error messages are fully externalized.
*
* @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
*/
@MessageBundle(projectCode = "TLIP")
public interface KeycloakMessages {
/**
* The messages
*/
KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class);
}
@@ -1,18 +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.
#
org.keycloak.subsystem.as7.KeycloakExtension
@@ -1,89 +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.
#
keycloak.subsystem=Keycloak adapter subsystem
keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
keycloak.subsystem.realm=A Keycloak realm.
keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
keycloak.realm=A Keycloak realm.
keycloak.realm.add=Add a realm definition to the subsystem.
keycloak.realm.remove=Remove a realm from the subsystem.
keycloak.realm.realm-public-key=Public key of the realm
keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
keycloak.realm.ssl-required=Specify if SSL is required (valid values are all, external and none)
keycloak.realm.allow-any-hostname=SSL Setting
keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
keycloak.realm.truststore-password=Password of the Truststore
keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
keycloak.realm.enable-cors=Enable Keycloak CORS support
keycloak.realm.client-keystore=n/a
keycloak.realm.client-keystore-password=n/a
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.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
keycloak.realm.register-node-at-startup=Cluster setting
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.secure-deployment=A deployment secured by Keycloak
keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
keycloak.secure-deployment.realm=Keycloak realm
keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
keycloak.secure-deployment.realm-public-key=Public key of the realm
keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
keycloak.secure-deployment.ssl-required=Specify if SSL is required (valid values are all, external and none)
keycloak.secure-deployment.allow-any-hostname=SSL Setting
keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
keycloak.secure-deployment.truststore-password=Password of the Truststore
keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
keycloak.secure-deployment.resource=Application name
keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
keycloak.secure-deployment.credentials=Adapter credentials
keycloak.secure-deployment.bearer-only=Bearer Token Auth only
keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
keycloak.secure-deployment.public-client=Public client
keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
keycloak.secure-deployment.client-keystore=n/a
keycloak.secure-deployment.client-keystore-password=n/a
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.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
keycloak.secure-deployment.register-node-at-startup=Cluster setting
keycloak.secure-deployment.register-node-period=how often to re-register node
keycloak.secure-deployment.token-store=cookie or session storage for auth session data
keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
keycloak.secure-deployment.turn-off-change-session-id-on-login=The session id is changed by default on a successful login. Change this to true if you want to turn this off
keycloak.secure-deployment.credential=Credential value
keycloak.credential=Credential
keycloak.credential.value=Credential value
keycloak.credential.add=Credential add
keycloak.credential.remove=Credential remove
@@ -1,122 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:jboss:domain:keycloak:1.1"
xmlns="urn:jboss:domain:keycloak:1.1"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0">
<!-- The subsystem root element -->
<xs:element name="subsystem" type="subsystem-type"/>
<xs:complexType name="subsystem-type">
<xs:annotation>
<xs:documentation>
<![CDATA[
The Keycloak adapter subsystem, used to register deployments managed by Keycloak
]]>
</xs:documentation>
</xs:annotation>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
<xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="realm-type">
<xs:all>
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>The name of the realm.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="secure-deployment-type">
<xs:all>
<xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
<xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="public-client" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
<xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
<xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
<xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
<xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
<xs:element name="turn-off-change-session-id-on-login" type="xs:boolean" minOccurs="0" maxOccurs="1" />
</xs:all>
<xs:attribute name="name" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>The name of the realm.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="credential-type" mixed="true">
<xs:sequence maxOccurs="unbounded" minOccurs="0">
<xs:any processContents="lax"></xs:any>
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:schema>
@@ -1,88 +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.subsystem.as7;
import org.jboss.dmr.ModelNode;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
*/
public class RealmDefinitionTestCase {
private ModelNode model;
@Before
public void setUp() {
model = new ModelNode();
model.get("realm").set("demo");
model.get("resource").set("customer-portal");
model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
model.get("auth-url").set("http://localhost:8080/auth-server/realms/demo/protocol/openid-connect/login");
model.get("code-url").set("http://localhost:8080/auth-server/realms/demo/protocol/openid-connect/access/codes");
model.get("expose-token").set(true);
ModelNode credential = new ModelNode();
credential.get("password").set("password");
model.get("credentials").set(credential);
}
@Test
public void testIsTruststoreSetIfRequired() throws Exception {
model.get("ssl-required").set("none");
model.get("disable-trust-manager").set(true);
Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("none");
model.get("disable-trust-manager").set(false);
Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("all");
model.get("disable-trust-manager").set(true);
Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("all");
model.get("disable-trust-manager").set(false);
Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("external");
model.get("disable-trust-manager").set(false);
Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("all");
model.get("disable-trust-manager").set(false);
model.get("truststore").set("foo");
Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("all");
model.get("disable-trust-manager").set(false);
model.get("truststore").set("foo");
model.get("truststore-password").set("password");
Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
model.get("ssl-required").set("external");
model.get("disable-trust-manager").set(false);
model.get("truststore").set("foo");
model.get("truststore-password").set("password");
Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
}
}
-38
View File
@@ -1,38 +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.
-->
<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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak AS7 / JBoss EAP 6 Integration</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-as7-integration-pom</artifactId>
<packaging>pom</packaging>
<modules>
<module>as7-adapter-spi</module>
<module>as7-adapter</module>
<module>as7-subsystem</module>
</modules>
</project>
-81
View File
@@ -1,81 +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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-installed-adapter</artifactId>
<name>Keycloak Installed Application</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</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>
</plugins>
</build>
</project>
-103
View File
@@ -1,103 +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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jaxrs-oauth-client</artifactId>
<name>Keycloak JAX-RS OAuth Client</name>
<description/>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.0_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,31 +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.jaxrs;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public interface JaxrsBearerTokenFilter extends ContainerRequestFilter {
}
@@ -1,314 +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.jaxrs;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.BasicAuthRequestAuthenticator;
import org.keycloak.adapters.BearerTokenRequestAuthenticator;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.common.constants.GenericConstants;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
private String keycloakConfigFile;
private String keycloakConfigResolverClass;
protected volatile boolean started;
protected AdapterDeploymentContext deploymentContext;
// TODO: Should also somehow handle stop lifecycle for de-registration
protected NodesRegistrationManagement nodesRegistrationManagement;
protected UserSessionManagement userSessionManagement = new EmptyUserSessionManagement();
public void setKeycloakConfigFile(String configFile) {
this.keycloakConfigFile = configFile;
attemptStart();
}
public String getKeycloakConfigFile() {
return this.keycloakConfigFile;
}
public String getKeycloakConfigResolverClass() {
return keycloakConfigResolverClass;
}
public void setKeycloakConfigResolverClass(String keycloakConfigResolverClass) {
this.keycloakConfigResolverClass = keycloakConfigResolverClass;
attemptStart();
}
// INITIALIZATION AND STARTUP
protected void attemptStart() {
if (started) {
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
}
if (isInitialized()) {
start();
} else {
log.fine("Not yet initialized");
}
}
protected boolean isInitialized() {
return this.keycloakConfigFile != null || this.keycloakConfigResolverClass != null;
}
protected void start() {
if (started) {
throw new IllegalStateException("Filter already started. Make sure to specify just keycloakConfigResolver or keycloakConfigFile but not both");
}
if (keycloakConfigResolverClass != null) {
Class<? extends KeycloakConfigResolver> resolverClass = loadResolverClass();
try {
KeycloakConfigResolver resolver = resolverClass.newInstance();
log.info("Using " + resolver + " to resolve Keycloak configuration on a per-request basis.");
this.deploymentContext = new AdapterDeploymentContext(resolver);
} catch (Exception e) {
throw new RuntimeException("Unable to instantiate resolver " + resolverClass);
}
} else {
if (keycloakConfigFile == null) {
throw new IllegalArgumentException("You need to specify either keycloakConfigResolverClass or keycloakConfigFile in configuration");
}
InputStream is = loadKeycloakConfigFile();
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(is);
deploymentContext = new AdapterDeploymentContext(kd);
log.info("Keycloak is using a per-deployment configuration loaded from: " + keycloakConfigFile);
}
nodesRegistrationManagement = new NodesRegistrationManagement();
started = true;
}
// TODO: Use 'Reflections.classForName'
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
try {
return (Class<? extends KeycloakConfigResolver>)getClass().getClassLoader().loadClass(keycloakConfigResolverClass);
} catch (ClassNotFoundException cnfe) {
// Fallback to tccl
try {
return (Class<? extends KeycloakConfigResolver>)Thread.currentThread().getContextClassLoader().loadClass(keycloakConfigResolverClass);
} catch (ClassNotFoundException cnfe2) {
throw new RuntimeException("Unable to find resolver class: " + keycloakConfigResolverClass);
}
}
}
protected InputStream loadKeycloakConfigFile() {
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
log.fine("Loading config from classpath on location: " + classPathLocation);
// Try current class classloader first
InputStream is = getClass().getClassLoader().getResourceAsStream(classPathLocation);
if (is == null) {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(classPathLocation);
}
if (is != null) {
return is;
} else {
throw new RuntimeException("Unable to find config from classpath: " + keycloakConfigFile);
}
} else {
// Fallback to file
try {
log.fine("Loading config from file: " + keycloakConfigFile);
return new FileInputStream(keycloakConfigFile);
} catch (FileNotFoundException fnfe) {
log.severe("Config not found on " + keycloakConfigFile);
throw new RuntimeException(fnfe);
}
}
}
// REQUEST HANDLING
@Override
public void filter(ContainerRequestContext request) throws IOException {
SecurityContext securityContext = getRequestSecurityContext(request);
JaxrsHttpFacade facade = new JaxrsHttpFacade(request, securityContext);
if (handlePreauth(facade)) {
return;
}
KeycloakDeployment resolvedDeployment = deploymentContext.resolveDeployment(facade);
nodesRegistrationManagement.tryRegister(resolvedDeployment);
bearerAuthentication(facade, request, resolvedDeployment);
}
protected boolean handlePreauth(JaxrsHttpFacade facade) {
PreAuthActionsHandler handler = new PreAuthActionsHandler(userSessionManagement, deploymentContext, facade);
if (handler.handleRequest()) {
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
return true;
}
return false;
}
protected void bearerAuthentication(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment) {
BearerTokenRequestAuthenticator authenticator = new BearerTokenRequestAuthenticator(resolvedDeployment);
AuthOutcome outcome = authenticator.authenticate(facade);
if (outcome == AuthOutcome.NOT_ATTEMPTED && resolvedDeployment.isEnableBasicAuth()) {
authenticator = new BasicAuthRequestAuthenticator(resolvedDeployment);
outcome = authenticator.authenticate(facade);
}
if (outcome == AuthOutcome.FAILED || outcome == AuthOutcome.NOT_ATTEMPTED) {
AuthChallenge challenge = authenticator.getChallenge();
log.fine("Authentication outcome: " + outcome);
boolean challengeSent = challenge.challenge(facade);
if (!challengeSent) {
// Use some default status code
facade.getResponse().setStatus(Response.Status.UNAUTHORIZED.getStatusCode());
}
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
return;
} else {
if (verifySslFailed(facade, resolvedDeployment)) {
return;
}
}
propagateSecurityContext(facade, request, resolvedDeployment, authenticator);
handleAuthActions(facade, resolvedDeployment);
}
protected void propagateSecurityContext(JaxrsHttpFacade facade, ContainerRequestContext request, KeycloakDeployment resolvedDeployment, BearerTokenRequestAuthenticator bearer) {
RefreshableKeycloakSecurityContext skSession = new RefreshableKeycloakSecurityContext(resolvedDeployment, null, bearer.getTokenString(), bearer.getToken(), null, null, null);
// Not needed to do resteasy specifics as KeycloakSecurityContext can be always retrieved from SecurityContext by typecast SecurityContext.getUserPrincipal to KeycloakPrincipal
// ResteasyProviderFactory.pushContext(KeycloakSecurityContext.class, skSession);
facade.setSecurityContext(skSession);
String principalName = AdapterUtils.getPrincipalName(resolvedDeployment, bearer.getToken());
final KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(principalName, skSession);
SecurityContext anonymousSecurityContext = getRequestSecurityContext(request);
final boolean isSecure = anonymousSecurityContext.isSecure();
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(skSession);
SecurityContext ctx = new SecurityContext() {
@Override
public Principal getUserPrincipal() {
return principal;
}
@Override
public boolean isUserInRole(String role) {
return roles.contains(role);
}
@Override
public boolean isSecure() {
return isSecure;
}
@Override
public String getAuthenticationScheme() {
return "OAUTH_BEARER";
}
};
request.setSecurityContext(ctx);
}
protected boolean verifySslFailed(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
log.warning("SSL is required to authenticate, but request is not secured");
facade.getResponse().sendError(403, "SSL required!");
return true;
}
return false;
}
protected SecurityContext getRequestSecurityContext(ContainerRequestContext request) {
return request.getSecurityContext();
}
protected void handleAuthActions(JaxrsHttpFacade facade, KeycloakDeployment deployment) {
AuthenticatedActionsHandler authActionsHandler = new AuthenticatedActionsHandler(deployment, facade);
if (authActionsHandler.handledRequest()) {
// Send response now (if not already sent)
if (!facade.isResponseFinished()) {
facade.getResponse().end();
}
}
}
// We don't have any sessions to manage with pure jaxrs filter
private static class EmptyUserSessionManagement implements UserSessionManagement {
@Override
public void logoutAll() {
}
@Override
public void logoutHttpSessions(List<String> ids) {
}
}
}
@@ -1,214 +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.jaxrs;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.security.cert.X509Certificate;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.SecurityContext;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
import org.keycloak.common.util.HostUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JaxrsHttpFacade implements OIDCHttpFacade {
protected final ContainerRequestContext requestContext;
protected final SecurityContext securityContext;
protected final RequestFacade requestFacade = new RequestFacade();
protected final ResponseFacade responseFacade = new ResponseFacade();
protected KeycloakSecurityContext keycloakSecurityContext;
protected boolean responseFinished;
public JaxrsHttpFacade(ContainerRequestContext containerRequestContext, SecurityContext securityContext) {
this.requestContext = containerRequestContext;
this.securityContext = securityContext;
}
protected class RequestFacade implements OIDCHttpFacade.Request {
@Override
public String getFirstParam(String param) {
throw new RuntimeException("NOT IMPLEMENTED");
}
@Override
public String getMethod() {
return requestContext.getMethod();
}
@Override
public String getURI() {
return requestContext.getUriInfo().getRequestUri().toString();
}
@Override
public boolean isSecure() {
return securityContext.isSecure();
}
@Override
public String getQueryParamValue(String param) {
MultivaluedMap<String, String> queryParams = requestContext.getUriInfo().getQueryParameters();
if (queryParams == null)
return null;
return queryParams.getFirst(param);
}
@Override
public Cookie getCookie(String cookieName) {
Map<String, javax.ws.rs.core.Cookie> cookies = requestContext.getCookies();
if (cookies == null)
return null;
javax.ws.rs.core.Cookie cookie = cookies.get(cookieName);
if (cookie == null)
return null;
return new Cookie(cookie.getName(), cookie.getValue(), cookie.getVersion(), cookie.getDomain(), cookie.getPath());
}
@Override
public String getHeader(String name) {
return requestContext.getHeaderString(name);
}
@Override
public List<String> getHeaders(String name) {
MultivaluedMap<String, String> headers = requestContext.getHeaders();
return (headers == null) ? null : headers.get(name);
}
@Override
public InputStream getInputStream() {
return requestContext.getEntityStream();
}
@Override
public String getRemoteAddr() {
// TODO: implement properly
return HostUtils.getIpAddress();
}
@Override
public void setError(AuthenticationError error) {
requestContext.setProperty(AuthenticationError.class.getName(), error);
}
@Override
public void setError(LogoutError error) {
requestContext.setProperty(LogoutError.class.getName(), error);
}
}
protected class ResponseFacade implements OIDCHttpFacade.Response {
private javax.ws.rs.core.Response.ResponseBuilder responseBuilder = javax.ws.rs.core.Response.status(204);
@Override
public void setStatus(int status) {
responseBuilder.status(status);
}
@Override
public void addHeader(String name, String value) {
responseBuilder.header(name, value);
}
@Override
public void setHeader(String name, String value) {
responseBuilder.header(name, value);
}
@Override
public void resetCookie(String name, String path) {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public void setCookie(String name, String value, String path, String domain, int maxAge, boolean secure, boolean httpOnly) {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public OutputStream getOutputStream() {
// For now doesn't need to be supported
throw new IllegalStateException("Not supported yet");
}
@Override
public void sendError(int code) {
javax.ws.rs.core.Response response = responseBuilder.status(code).build();
requestContext.abortWith(response);
responseFinished = true;
}
@Override
public void sendError(int code, String message) {
javax.ws.rs.core.Response response = responseBuilder.status(code).entity(message).build();
requestContext.abortWith(response);
responseFinished = true;
}
@Override
public void end() {
javax.ws.rs.core.Response response = responseBuilder.build();
requestContext.abortWith(response);
responseFinished = true;
}
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return keycloakSecurityContext;
}
public void setSecurityContext(KeycloakSecurityContext securityContext) {
this.keycloakSecurityContext = securityContext;
}
@Override
public Request getRequest() {
return requestFacade;
}
@Override
public Response getResponse() {
return responseFacade;
}
@Override
public X509Certificate[] getCertificateChain() {
throw new IllegalStateException("Not supported yet");
}
public boolean isResponseFinished() {
return responseFinished;
}
}
@@ -1,95 +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.jaxrs;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.logging.Logger;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.PreMatching;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.common.constants.GenericConstants;
import org.osgi.framework.BundleContext;
/**
* Variant of JaxrsBearerTokenFilter, which can be used to properly use resources from current osgi bundle
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@PreMatching
@Priority(Priorities.AUTHENTICATION)
public class OsgiJaxrsBearerTokenFilterImpl extends JaxrsBearerTokenFilterImpl {
private final static Logger log = Logger.getLogger("" + JaxrsBearerTokenFilterImpl.class);
private BundleContext bundleContext;
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
attemptStart();
}
@Override
protected boolean isInitialized() {
return super.isInitialized() && bundleContext != null;
}
@Override
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
String resolverClass = getKeycloakConfigResolverClass();
try {
return (Class<? extends KeycloakConfigResolver>) bundleContext.getBundle().loadClass(resolverClass);
} catch (ClassNotFoundException cnfe) {
log.warning("Not able to find class from bundleContext. Fallback to current classloader");
return super.loadResolverClass();
}
}
@Override
protected InputStream loadKeycloakConfigFile() {
String keycloakConfigFile = getKeycloakConfigFile();
if (keycloakConfigFile.startsWith(GenericConstants.PROTOCOL_CLASSPATH)) {
// Load from classpath of current bundle
String classPathLocation = keycloakConfigFile.replace(GenericConstants.PROTOCOL_CLASSPATH, "");
log.fine("Loading config from classpath on location: " + classPathLocation);
URL cfgUrl = bundleContext.getBundle().getResource(classPathLocation);
if (cfgUrl == null) {
log.warning("Not able to find configFile from bundleContext. Fallback to current classloader");
return super.loadKeycloakConfigFile();
}
try {
return cfgUrl.openStream();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
} else {
return super.loadKeycloakConfigFile();
}
}
}
-160
View File
@@ -1,160 +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>1.9.7.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty-core</artifactId>
<name>Keycloak Jetty Core Integration</name>
<properties>
<jetty9.version>8.1.17.v20150415</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.core.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
javax.servlet.*;version="[2.5,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,357 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.security.DefaultUserIdentity;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterDeploymentContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.AuthenticatedActionsHandler;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.NodesRegistrationManagement;
import org.keycloak.adapters.PreAuthActionsHandler;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.enums.TokenStore;
import org.keycloak.representations.adapters.config.AdapterConfig;
import javax.security.auth.Subject;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public abstract class AbstractKeycloakJettyAuthenticator extends LoginAuthenticator {
public static final String TOKEN_STORE_NOTE = "TOKEN_STORE_NOTE";
protected static final org.jboss.logging.Logger log = Logger.getLogger(AbstractKeycloakJettyAuthenticator.class);
protected AdapterDeploymentContext deploymentContext;
protected NodesRegistrationManagement nodesRegistrationManagement;
protected AdapterConfig adapterConfig;
protected KeycloakConfigResolver configResolver;
protected String errorPage;
public AbstractKeycloakJettyAuthenticator() {
super();
}
private static InputStream getJSONFromServletContext(ServletContext servletContext) {
String json = servletContext.getInitParameter(AdapterConstants.AUTH_DATA_PARAM_NAME);
if (json == null) {
return null;
}
return new ByteArrayInputStream(json.getBytes());
}
public AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
AdapterTokenStore store = (AdapterTokenStore) request.getAttribute(TOKEN_STORE_NOTE);
if (store != null) {
return store;
}
if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
store = createSessionTokenStore(request, resolvedDeployment);
} else {
store = new JettyCookieTokenStore(request, facade, resolvedDeployment);
}
request.setAttribute(TOKEN_STORE_NOTE, store);
return store;
}
public abstract AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment);
public void logoutCurrent(Request request) {
AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) request.getAttribute(AdapterDeploymentContext.class.getName());
KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());
if (ksc != null) {
JettyHttpFacade facade = new OIDCJettyHttpFacade(request, null);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (ksc instanceof RefreshableKeycloakSecurityContext) {
((RefreshableKeycloakSecurityContext) ksc).logout(deployment);
}
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
tokenStore.logout();
request.removeAttribute(KeycloakSecurityContext.class.getName());
}
}
public static UserIdentity createIdentity(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(principal.getKeycloakSecurityContext());
if (roles == null) {
roles = new HashSet<String>();
}
Subject theSubject = new Subject();
String[] theRoles = new String[roles.size()];
roles.toArray(theRoles);
return new DefaultUserIdentity(theSubject, principal, theRoles);
}
private class DummyLoginService implements LoginService {
@Override
public String getName() {
return null;
}
@Override
public UserIdentity login(String username, Object credentials) {
return null;
}
@Override
public boolean validate(UserIdentity user) {
return false;
}
@Override
public IdentityService getIdentityService() {
return null;
}
@Override
public void setIdentityService(IdentityService service) {
}
@Override
public void logout(UserIdentity user) {
}
}
@Override
public void setConfiguration(AuthConfiguration configuration) {
//super.setConfiguration(configuration);
initializeKeycloak();
// need this so that getUserPrincipal does not throw NPE
_loginService = new DummyLoginService();
String error = configuration.getInitParameter(FormAuthenticator.__FORM_ERROR_PAGE);
setErrorPage(error);
}
private void setErrorPage(String path) {
if (path == null || path.trim().length() == 0) {
} else {
if (!path.startsWith("/")) {
path = "/" + path;
}
errorPage = path;
if (errorPage.indexOf('?') > 0)
errorPage = errorPage.substring(0, errorPage.indexOf('?'));
}
}
@Override
public boolean secureResponse(ServletRequest req, ServletResponse res, boolean mandatory, Authentication.User validatedUser) throws ServerAuthException {
return true;
}
public AdapterConfig getAdapterConfig() {
return adapterConfig;
}
public void setAdapterConfig(AdapterConfig adapterConfig) {
this.adapterConfig = adapterConfig;
}
public KeycloakConfigResolver getConfigResolver() {
return configResolver;
}
public void setConfigResolver(KeycloakConfigResolver configResolver) {
this.configResolver = configResolver;
}
@SuppressWarnings("UseSpecificCatch")
public void initializeKeycloak() {
nodesRegistrationManagement = new NodesRegistrationManagement();
ServletContext theServletContext = null;
ContextHandler.Context currentContext = ContextHandler.getCurrentContext();
if (currentContext != null) {
String contextPath = currentContext.getContextPath();
if ("".equals(contextPath)) {
// This could be the case in osgi environment when deploying apps through pax whiteboard extension.
theServletContext = currentContext;
} else {
theServletContext = currentContext.getContext(contextPath);
}
}
// Jetty 9.1.x servlet context will be null :(
if (configResolver == null && theServletContext != null) {
String configResolverClass = theServletContext.getInitParameter("keycloak.config.resolver");
if (configResolverClass != null) {
try {
configResolver = (KeycloakConfigResolver) ContextHandler.getCurrentContext().getClassLoader().loadClass(configResolverClass).newInstance();
log.infov("Using {0} to resolve Keycloak configuration on a per-request basis.", configResolverClass);
} catch (Exception ex) {
log.infov("The specified resolver {0} could NOT be loaded. Keycloak is unconfigured and will deny all requests. Reason: {1}", new Object[]{configResolverClass, ex.getMessage()});
}
}
}
if (configResolver != null) {
deploymentContext = new AdapterDeploymentContext(configResolver);
} else if (adapterConfig != null) {
KeycloakDeployment kd = KeycloakDeploymentBuilder.build(adapterConfig);
deploymentContext = new AdapterDeploymentContext(kd);
} else if (theServletContext != null) {
InputStream configInputStream = getConfigInputStream(theServletContext);
if (configInputStream != null) {
deploymentContext = new AdapterDeploymentContext(KeycloakDeploymentBuilder.build(configInputStream));
}
}
if (deploymentContext == null) {
deploymentContext = new AdapterDeploymentContext(new KeycloakDeployment());
}
if (theServletContext != null)
theServletContext.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
}
private InputStream getConfigInputStream(ServletContext servletContext) {
InputStream is = getJSONFromServletContext(servletContext);
if (is == null) {
String path = servletContext.getInitParameter("keycloak.config.file");
if (path == null) {
is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json");
} else {
try {
is = new FileInputStream(path);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}
return is;
}
@Override
public Authentication validateRequest(ServletRequest req, ServletResponse res, boolean mandatory) throws ServerAuthException {
if (log.isTraceEnabled()) {
log.trace("*** authenticate");
}
Request request = resolveRequest(req);
OIDCJettyHttpFacade facade = new OIDCJettyHttpFacade(request, (HttpServletResponse) res);
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
if (deployment == null || !deployment.isConfigured()) {
log.debug("*** deployment isn't configured return false");
return Authentication.UNAUTHENTICATED;
}
PreAuthActionsHandler handler = new PreAuthActionsHandler(new JettyUserSessionManagement(request.getSessionManager()), deploymentContext, facade);
if (handler.handleRequest()) {
return Authentication.SEND_SUCCESS;
}
if (!mandatory)
return new DeferredAuthentication(this);
AdapterTokenStore tokenStore = getTokenStore(request, facade, deployment);
nodesRegistrationManagement.tryRegister(deployment);
tokenStore.checkCurrentToken();
JettyRequestAuthenticator authenticator = createRequestAuthenticator(request, facade, deployment, tokenStore);
AuthOutcome outcome = authenticator.authenticate();
if (outcome == AuthOutcome.AUTHENTICATED) {
if (facade.isEnded()) {
return Authentication.SEND_SUCCESS;
}
Authentication authentication = register(request, authenticator.principal);
AuthenticatedActionsHandler authenticatedActionsHandler = new AuthenticatedActionsHandler(deployment, facade);
if (authenticatedActionsHandler.handledRequest()) {
return Authentication.SEND_SUCCESS;
}
return authentication;
}
AuthChallenge challenge = authenticator.getChallenge();
if (challenge != null) {
challenge.challenge(facade);
}
return Authentication.SEND_CONTINUE;
}
protected abstract Request resolveRequest(ServletRequest req);
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new JettyRequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
@Override
public String getAuthMethod() {
return "KEYCLOAK";
}
protected Authentication register(Request request, KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal) {
request.setAttribute(AdapterDeploymentContext.class.getName(), deploymentContext);
Authentication authentication = request.getAuthentication();
if (!(authentication instanceof KeycloakAuthentication)) {
UserIdentity userIdentity = createIdentity(principal);
authentication = createAuthentication(userIdentity);
request.setAuthentication(authentication);
}
return authentication;
}
protected abstract Authentication createAuthentication(UserIdentity userIdentity);
public static abstract class KeycloakAuthentication extends UserAuthentication {
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
super(method, userIdentity);
}
}
}
@@ -1,129 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.server.Request;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.CookieTokenStore;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
/**
* Handle storage of token info in cookie. Per-request object.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JettyCookieTokenStore implements AdapterTokenStore {
private static final Logger log = Logger.getLogger(JettyCookieTokenStore.class);
private Request request;
private HttpFacade facade;
private KeycloakDeployment deployment;
private KeycloakPrincipal<RefreshableKeycloakSecurityContext> authenticatedPrincipal;
public JettyCookieTokenStore(Request request, HttpFacade facade, KeycloakDeployment deployment) {
this.request = request;
this.facade = facade;
this.deployment = deployment;
}
@Override
public void checkCurrentToken() {
this.authenticatedPrincipal = checkPrincipalFromCookie();
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
// Assuming authenticatedPrincipal set by previous call of checkCurrentToken() during this request
if (authenticatedPrincipal != null) {
log.debug("remote logged in already. Establish state from cookie");
RefreshableKeycloakSecurityContext securityContext = authenticatedPrincipal.getKeycloakSecurityContext();
if (!securityContext.getRealm().equals(deployment.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
return true;
} else {
return false;
}
}
@Override
public void saveAccountInfo(OidcKeycloakAccount account) {
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext)account.getKeycloakSecurityContext();
CookieTokenStore.setTokenCookie(deployment, facade, securityContext);
}
@Override
public void logout() {
CookieTokenStore.removeCookie(facade);
}
@Override
public void refreshCallback(RefreshableKeycloakSecurityContext secContext) {
CookieTokenStore.setTokenCookie(deployment, facade, secContext);
}
/**
* Verify if we already have authenticated and active principal in cookie. Perform refresh if it's not active
*
* @return valid principal
*/
protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> checkPrincipalFromCookie() {
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = CookieTokenStore.getPrincipalFromCookie(deployment, facade, this);
if (principal == null) {
log.debug("Account was not in cookie or was invalid");
return null;
}
RefreshableKeycloakSecurityContext session = principal.getKeycloakSecurityContext();
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return principal;
boolean success = session.refreshExpiredToken(false);
if (success && session.isActive()) return principal;
log.debugf("Cleanup and expire cookie for user %s after failed refresh", principal.getName());
CookieTokenStore.removeCookie(facade);
return null;
}
@Override
public void saveRequest() {
}
@Override
public boolean restoreRequest() {
return false;
}
}
@@ -1,102 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.server.Request;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OAuthRequestAuthenticator;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import javax.servlet.http.HttpSession;
import java.security.Principal;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyRequestAuthenticator extends RequestAuthenticator {
protected static final Logger log = Logger.getLogger(JettyRequestAuthenticator.class);
protected Request request;
protected KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal;
public JettyRequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort);
this.request = request;
}
@Override
protected OAuthRequestAuthenticator createOAuthAuthenticator() {
return new OAuthRequestAuthenticator(this, facade, deployment, sslRedirectPort, tokenStore);
}
@Override
protected void completeOAuthAuthentication(final KeycloakPrincipal<RefreshableKeycloakSecurityContext> skp) {
principal = skp;
final RefreshableKeycloakSecurityContext securityContext = skp.getKeycloakSecurityContext();
final Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
OidcKeycloakAccount account = new OidcKeycloakAccount() {
@Override
public Principal getPrincipal() {
return skp;
}
@Override
public Set<String> getRoles() {
return roles;
}
@Override
public KeycloakSecurityContext getKeycloakSecurityContext() {
return securityContext;
}
};
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
this.tokenStore.saveAccountInfo(account);
}
@Override
protected void completeBearerAuthentication(KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal, String method) {
this.principal = principal;
RefreshableKeycloakSecurityContext securityContext = principal.getKeycloakSecurityContext();
Set<String> roles = AdapterUtils.getRolesFromSecurityContext(securityContext);
if (log.isDebugEnabled()) {
log.debug("Completing bearer authentication. Bearer roles: " + roles);
}
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
@Override
protected String changeHttpSessionId(boolean create) {
HttpSession session = request.getSession(create);
return session != null ? session.getId() : null;
}
}
@@ -1,125 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty.core;
import org.eclipse.jetty.server.Request;
import org.jboss.logging.Logger;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.AdapterUtils;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.OidcKeycloakAccount;
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
import org.keycloak.adapters.RequestAuthenticator;
import javax.servlet.http.HttpSession;
/**
* Handle storage of token info in HTTP Session. Per-request object
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JettySessionTokenStore implements AdapterTokenStore {
private static final Logger log = Logger.getLogger(JettySessionTokenStore.class);
private Request request;
protected KeycloakDeployment deployment;
protected AdapterSessionStore sessionStore;
public JettySessionTokenStore(Request request, KeycloakDeployment deployment, AdapterSessionStore sessionStore) {
this.request = request;
this.deployment = deployment;
this.sessionStore = sessionStore;
}
@Override
public void checkCurrentToken() {
if (request.getSession(false) == null) return;
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName());
if (session == null) return;
// just in case session got serialized
if (session.getDeployment() == null) session.setCurrentRequestInfo(deployment, this);
if (session.isActive() && !session.getDeployment().isAlwaysRefreshToken()) return;
// FYI: A refresh requires same scope, so same roles will be set. Otherwise, refresh will fail and token will
// not be updated
boolean success = session.refreshExpiredToken(false);
if (success && session.isActive()) return;
// Refresh failed, so user is already logged out from keycloak. Cleanup and expire our session
request.getSession().removeAttribute(KeycloakSecurityContext.class.getName());
request.getSession().invalidate();
}
@Override
public boolean isCached(RequestAuthenticator authenticator) {
if (request.getSession(false) == null || request.getSession().getAttribute(KeycloakSecurityContext.class.getName()) == null)
return false;
log.debug("remote logged in already. Establish state from session");
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) request.getSession().getAttribute(KeycloakSecurityContext.class.getName());
if (!deployment.getRealm().equals(securityContext.getRealm())) {
log.debug("Account from cookie is from a different realm than for the request.");
return false;
}
securityContext.setCurrentRequestInfo(deployment, this);
request.setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
JettyRequestAuthenticator jettyAuthenticator = (JettyRequestAuthenticator) authenticator;
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal = AdapterUtils.createPrincipal(deployment, securityContext);
jettyAuthenticator.principal = principal;
restoreRequest();
return true;
}
@Override
public void saveAccountInfo(OidcKeycloakAccount account) {
RefreshableKeycloakSecurityContext securityContext = (RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
request.getSession().setAttribute(KeycloakSecurityContext.class.getName(), securityContext);
}
@Override
public void logout() {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(KeycloakSecurityContext.class.getName());
}
}
@Override
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
// no-op
}
@Override
public void saveRequest() {
sessionStore.saveRequest();
}
@Override
public boolean restoreRequest() {
return sessionStore.restoreRequest();
}
}
@@ -1,41 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty.core;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.OIDCHttpFacade;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import javax.servlet.http.HttpServletResponse;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OIDCJettyHttpFacade extends JettyHttpFacade implements OIDCHttpFacade {
public OIDCJettyHttpFacade(org.eclipse.jetty.server.Request request, HttpServletResponse response) {
super(request, response);
}
@Override
public KeycloakSecurityContext getSecurityContext() {
return (KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
}
}
-159
View File
@@ -1,159 +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>1.9.7.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty81-adapter</artifactId>
<name>Keycloak Jetty 8.1.x Integration</name>
<properties>
<jetty9.version>8.1.17.v20150415</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
javax.servlet.*;version="[2.5,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
MultiMap<String> formParameters = new MultiMap<String>();
base_request.extractParameters();
return base_request.getParameters();
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,63 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.AbstractHttpConnection;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request)?(Request)req: AbstractHttpConnection.getCurrentConnection().getRequest();
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(AbstractHttpConnection.getCurrentConnection().getRequest());
}
};
}
}
-170
View File
@@ -1,170 +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>1.9.7.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty91-adapter</artifactId>
<name>Keycloak Jetty 9.1.x Integration</name>
<properties>
<jetty9.version>9.1.5.v20140505</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;version="[9.1,9.2)";resolution:=optional,
javax.servlet.*;version="[3.0,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
</exclusion>
<exclusion>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Request;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty91RequestAuthenticator extends JettyRequestAuthenticator {
public Jetty91RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort, request);
}
@Override
protected String changeHttpSessionId(boolean create) {
Request request = this.request;
HttpSession session = request.getSession(false);
if (session == null) {
return request.getSession(true).getId();
}
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}
@@ -1,111 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
base_request.extractParameters();
return base_request.getParameters();
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,72 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(HttpChannel.getCurrentHttpChannel().getRequest());
}
};
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new Jetty91RequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
}
-156
View File
@@ -1,156 +0,0 @@
<?xml version="1.0"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.9.7.Final</version>
<relativePath>../../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty92-adapter</artifactId>
<name>Keycloak Jetty 9.2.x Integration</name>
<properties>
<jetty9.version>9.2.4.v20141103</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.jetty.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.eclipse.jetty.*;resolution:=optional,
javax.servlet.*;version="[3.0,4)";resolution:=optional,
org.keycloak.*;version="${project.version}",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<description />
<dependencies>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${jboss.logging.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-jetty-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Request;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.spi.HttpFacade;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Jetty92RequestAuthenticator extends JettyRequestAuthenticator {
public Jetty92RequestAuthenticator(HttpFacade facade, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort, Request request) {
super(facade, deployment, tokenStore, sslRedirectPort, request);
}
@Override
protected String changeHttpSessionId(boolean create) {
Request request = this.request;
HttpSession session = request.getSession(false);
if (session == null) {
return request.getSession(true).getId();
}
if (!deployment.isTurnOffChangeSessionIdOnLogin()) return request.changeSessionId();
else return session.getId();
}
}
@@ -1,112 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.authentication.FormAuthenticator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.MultiMap;
import org.keycloak.adapters.spi.AdapterSessionStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import org.keycloak.common.util.MultivaluedHashMap;
import javax.servlet.http.HttpSession;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JettyAdapterSessionStore implements AdapterSessionStore {
public static final String CACHED_FORM_PARAMETERS = "__CACHED_FORM_PARAMETERS";
protected Request myRequest;
public JettyAdapterSessionStore(Request request) {
this.myRequest = request; // for IDE/compilation purposes
}
protected MultiMap<String> extractFormParameters(Request base_request) {
MultiMap<String> formParameters = new MultiMap<String>();
base_request.extractFormParameters(formParameters);
return formParameters;
}
protected void restoreFormParameters(MultiMap<String> j_post, Request base_request) {
base_request.setContentParameters(j_post);
}
public boolean restoreRequest() {
HttpSession session = myRequest.getSession(false);
if (session == null) return false;
synchronized (session) {
String j_uri = (String) session.getAttribute(FormAuthenticator.__J_URI);
if (j_uri != null) {
// check if the request is for the same url as the original and restore
// params if it was a post
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
if (j_uri.equals(buf.toString())) {
String method = (String)session.getAttribute(JettyHttpFacade.__J_METHOD);
myRequest.setMethod(HttpMethod.valueOf(method.toUpperCase()), method);
MultivaluedHashMap<String, String> j_post = (MultivaluedHashMap<String, String>) session.getAttribute(CACHED_FORM_PARAMETERS);
if (j_post != null) {
myRequest.setContentType("application/x-www-form-urlencoded");
MultiMap<String> map = new MultiMap<String>();
for (String key : j_post.keySet()) {
for (String val : j_post.getList(key)) {
map.add(key, val);
}
}
restoreFormParameters(map, myRequest);
}
session.removeAttribute(FormAuthenticator.__J_URI);
session.removeAttribute(JettyHttpFacade.__J_METHOD);
session.removeAttribute(FormAuthenticator.__J_POST);
}
return true;
}
}
return false;
}
public void saveRequest() {
// remember the current URI
HttpSession session = myRequest.getSession();
synchronized (session) {
// But only if it is not set already, or we save every uri that leads to a login form redirect
if (session.getAttribute(FormAuthenticator.__J_URI) == null) {
StringBuffer buf = myRequest.getRequestURL();
if (myRequest.getQueryString() != null)
buf.append("?").append(myRequest.getQueryString());
session.setAttribute(FormAuthenticator.__J_URI, buf.toString());
session.setAttribute(JettyHttpFacade.__J_METHOD, myRequest.getMethod());
if ("application/x-www-form-urlencoded".equals(myRequest.getContentType()) && "POST".equalsIgnoreCase(myRequest.getMethod())) {
MultiMap<String> formParameters = extractFormParameters(myRequest);
MultivaluedHashMap<String, String> map = new MultivaluedHashMap<String, String>();
for (String key : formParameters.keySet()) {
for (Object value : formParameters.getValues(key)) {
map.add(key, (String) value);
}
}
session.setAttribute(CACHED_FORM_PARAMETERS, map);
}
}
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.jetty;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.UserIdentity;
import org.keycloak.adapters.AdapterTokenStore;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.jetty.core.AbstractKeycloakJettyAuthenticator;
import org.keycloak.adapters.jetty.core.JettyRequestAuthenticator;
import org.keycloak.adapters.jetty.core.JettySessionTokenStore;
import org.keycloak.adapters.jetty.spi.JettyHttpFacade;
import javax.servlet.ServletRequest;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakJettyAuthenticator extends AbstractKeycloakJettyAuthenticator {
public KeycloakJettyAuthenticator() {
super();
}
@Override
protected Request resolveRequest(ServletRequest req) {
return (req instanceof Request) ? (Request)req : HttpChannel.getCurrentHttpChannel().getRequest();
}
@Override
protected Authentication createAuthentication(UserIdentity userIdentity) {
return new KeycloakAuthentication(getAuthMethod(), userIdentity) {
@Override
public void logout() {
logoutCurrent(HttpChannel.getCurrentHttpChannel().getRequest());
}
};
}
@Override
public AdapterTokenStore createSessionTokenStore(Request request, KeycloakDeployment resolvedDeployment) {
return new JettySessionTokenStore(request, resolvedDeployment, new JettyAdapterSessionStore(request));
}
@Override
protected JettyRequestAuthenticator createRequestAuthenticator(Request request, JettyHttpFacade facade,
KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
return new Jetty92RequestAuthenticator(facade, deployment, tokenStore, -1, request);
}
}
-39
View File
@@ -1,39 +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.
-->
<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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<name>Keycloak Jetty Integration</name>
<description/>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-jetty-integration-pom</artifactId>
<packaging>pom</packaging>
<modules>
<module>jetty-core</module>
<module>jetty8.1</module>
<module>jetty9.2</module>
<module>jetty9.1</module>
</modules>
</project>
-66
View File
@@ -1,66 +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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-js-adapter</artifactId>
<name>Keycloak JS Integration</name>
<description />
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.samaxes.maven</groupId>
<artifactId>minify-maven-plugin</artifactId>
<executions>
<execution>
<id>min-js</id>
<phase>compile</phase>
<configuration>
<charset>utf-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<jsSourceDir>.</jsSourceDir>
<jsSourceFiles>
<jsSourceFile>keycloak.js</jsSourceFile>
</jsSourceFiles>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<jsTargetDir>.</jsTargetDir>
<jsFinalFile>keycloak.js</jsFinalFile>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
File diff suppressed because it is too large. Load diff
@@ -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.
-->
<script>
function getCookie(cname)
{
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++)
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return null;
}
function receiveMessage(event)
{
if (event.origin !== "ORIGIN") {
console.log(event.origin + " does not match built origin");
return;
}
var data = JSON.parse(event.data);
data.loggedIn = false;
var cookie = getCookie('KEYCLOAK_SESSION');
if (cookie) {
data.loggedIn = true;
data.session = cookie;
}
event.source.postMessage(JSON.stringify(data),
event.origin);
}
window.addEventListener("message", receiveMessage, false);
</script>
-149
View File
@@ -1,149 +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>1.9.7.Final</version>
<relativePath>../../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-osgi-adapter</artifactId>
<name>Keycloak OSGI Adapter</name>
<packaging>jar</packaging>
<properties>
<jetty9.version>8.1.17.v20150415</jetty9.version>
<keycloak.osgi.export>
org.keycloak.adapters.osgi.*
</keycloak.osgi.export>
<keycloak.osgi.import>
org.ops4j.pax.web.*;version="[3.0,5)",
javax.servlet.*;version="[2.5,4)";resolution:=optional,
org.eclipse.jetty.*;version="[8.1,10)";resolution:=optional,
org.keycloak.*;version="${project.version}",
org.apache.cxf.transport.http;resolution:=optional;version="[3,4)",
org.apache.cxf.transport.servlet;resolution:=optional;version="[3,4)",
*;resolution:=optional
</keycloak.osgi.import>
</properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.enterprise</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-runtime</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.web</groupId>
<artifactId>pax-web-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<id>bundle-manifest</id>
<phase>process-classes</phase>
<goals>
<goal>manifest</goal>
</goals>
</execution>
</executions>
<configuration>
<instructions>
<Bundle-ClassPath>.</Bundle-ClassPath>
<Bundle-Name>${project.name}</Bundle-Name>
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
<Import-Package>${keycloak.osgi.import}</Import-Package>
<Export-Package>${keycloak.osgi.export}</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,71 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.osgi;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.OIDCHttpFacade;
public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
@Override
public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
String path = request.getURI();
String[] urlTokens = path.split("/");
if (urlTokens.length < 4) {
throw new IllegalStateException("Not able to determine the web-context to load the correspondent keycloak.json file");
}
String webContext = urlTokens[3];
KeycloakDeployment deployment = cache.get(webContext);
if (null == deployment) {
// not found on the simple cache, try to load it from the file system
String keycloakConfig = (String) System.getProperties().get("keycloak.config");
if(keycloakConfig == null || "".equals(keycloakConfig.trim())){
String karafEtc = (String) System.getProperties().get("karaf.etc");
if(karafEtc == null || "".equals(karafEtc.trim())){
throw new IllegalStateException("Neither \"keycloak.config\" nor \"karaf.etc\" java properties are set. Please set one of them.");
}
keycloakConfig = karafEtc;
}
String absolutePath = keycloakConfig + File.separator + webContext + "-keycloak.json";
InputStream is = null;
try {
is = new FileInputStream(absolutePath);
} catch (FileNotFoundException e){
throw new IllegalStateException("Not able to find the file " + absolutePath);
}
deployment = KeycloakDeploymentBuilder.build(is);
cache.put(webContext, deployment);
}
return deployment;
}
}
@@ -1,181 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.adapters.osgi;
import java.net.URL;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.util.security.Constraint;
import org.jboss.logging.Logger;
import org.ops4j.pax.web.service.WebContainer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpContext;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer}
* and allows to inject custom security constraint for securing resources by Keycloak.
*
* <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which
* is true in karaf/fuse environment</p>
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PaxWebIntegrationService {
protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class);
private BundleContext bundleContext;
private String jettyWebXmlLocation;
private List<ConstraintMapping> constraintMappings; // Using jetty constraint mapping just because of compatibility with other fuse services
private ServiceTracker webContainerTracker;
private HttpContext httpContext;
public BundleContext getBundleContext() {
return bundleContext;
}
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
public String getJettyWebXmlLocation() {
return jettyWebXmlLocation;
}
public void setJettyWebXmlLocation(String jettyWebXmlLocation) {
this.jettyWebXmlLocation = jettyWebXmlLocation;
}
public List<ConstraintMapping> getConstraintMappings() {
return constraintMappings;
}
public void setConstraintMappings(List<ConstraintMapping> constraintMappings) {
this.constraintMappings = constraintMappings;
}
protected ServiceTracker getWebContainerTracker() {
return webContainerTracker;
}
protected HttpContext getHttpContext() {
return httpContext;
}
public void start() {
ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() {
@Override
public Object addingService(ServiceReference reference) {
return addingWebContainerCallback(reference);
}
@Override
public void modifiedService(ServiceReference reference, Object service) {
}
@Override
public void removedService(ServiceReference reference, Object service) {
removingWebContainerCallback(reference);
}
};
webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer);
webContainerTracker.open();
}
public void stop() {
webContainerTracker.remove(webContainerTracker.getServiceReference());
}
protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) {
WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference);
httpContext = service.createDefaultHttpContext();
addJettyWebXml(service);
if (constraintMappings == null) {
throw new IllegalStateException("constraintMappings was null!");
}
for (ConstraintMapping constraintMapping : constraintMappings) {
addConstraintMapping(service, constraintMapping);
}
service.registerLoginConfig("BASIC", "does-not-matter", null, null, httpContext);
return service;
}
protected void addJettyWebXml(WebContainer service) {
String jettyWebXmlLoc;
if (this.jettyWebXmlLocation == null) {
jettyWebXmlLoc = "/WEB-INF/jetty-web.xml";
} else {
jettyWebXmlLoc = this.jettyWebXmlLocation;
}
URL jettyWebXml = bundleContext.getBundle().getResource(jettyWebXmlLoc);
if (jettyWebXml != null) {
log.debug("Found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
service.registerJettyWebXml(jettyWebXml, httpContext);
} else {
log.debug("Not found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc);
}
}
protected void addConstraintMapping(WebContainer service, ConstraintMapping constraintMapping) {
Constraint constraint = constraintMapping.getConstraint();
String[] roles = constraint.getRoles();
// name property is unavailable on constraint object :/
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
int dataConstraint = constraint.getDataConstraint();
String dataConstraintStr;
switch (dataConstraint) {
case Constraint.DC_UNSET: dataConstraintStr = null; break;
case Constraint.DC_NONE: dataConstraintStr = "NONE"; break;
case Constraint.DC_CONFIDENTIAL: dataConstraintStr = "CONFIDENTIAL"; break;
case Constraint.DC_INTEGRAL: dataConstraintStr = "INTEGRAL"; break;
default:
log.warnv("Unknown data constraint: " + dataConstraint);
dataConstraintStr = "CONFIDENTIAL";
}
List<String> rolesList = Arrays.asList(roles);
log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate="
+ constraint.getAuthenticate() + ", roles=" + rolesList);
service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext);
}
protected void removingWebContainerCallback(ServiceReference serviceReference) {
WebContainer service = (WebContainer)bundleContext.getService(serviceReference);
if (service != null) {
service.unregisterLoginConfig(httpContext);
service.unregisterConstraintMapping(httpContext);
}
}
}
Loaded 100 of 6286 files, more files were not shown because too many files have changed in this diff. Show more