Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5304e197e5 | ||
|
|
623e28b9c2 | ||
|
|
274bc4e33a | ||
|
|
42b44ed3b0 | ||
|
|
b0c99e6111 | ||
|
|
fe73feee34 | ||
|
|
629d3ce4aa | ||
|
|
ed6c290bbc | ||
|
|
9dde3f60d6 | ||
|
|
1bcc8c9110 | ||
|
|
bb132e1aa0 | ||
|
|
5e4b843eb7 | ||
|
|
85cc63c7ce | ||
|
|
a697f4bd41 | ||
|
|
7e03c74bc3 | ||
|
|
67f7bcec99 | ||
|
|
267b80c2fa | ||
|
|
66e848710e | ||
|
|
93f48ecb6c | ||
|
|
ecace349f5 | ||
|
|
4a5b313816 | ||
|
|
c79506a34f | ||
|
|
5e97051c07 | ||
|
|
562aa24ebf | ||
|
|
4a66687304 | ||
|
|
42c2066fd8 | ||
|
|
161b3293dc | ||
|
|
a9608904d0 | ||
|
|
1b9358c4c6 | ||
|
|
70ff6c7209 | ||
|
|
3133e30265 | ||
|
|
d0c2e80949 | ||
|
|
89e1937501 |
No files matched your search
@@ -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
|
||||
-10
@@ -9,13 +9,6 @@
|
||||
.settings
|
||||
.classpath
|
||||
|
||||
# NetBeans #
|
||||
############
|
||||
nbactions.xml
|
||||
nb-configuration.xml
|
||||
catalog.xml
|
||||
nbproject
|
||||
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
@@ -46,6 +39,3 @@ nbproject
|
||||
#########
|
||||
target
|
||||
|
||||
# Maven shade
|
||||
#############
|
||||
*dependency-reduced-pom.xml
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
language: java
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
before_cache:
|
||||
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||
|
||||
env:
|
||||
global:
|
||||
- MAVEN_SKIP_RC=true
|
||||
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
||||
matrix:
|
||||
- TESTS=group1
|
||||
- TESTS=group2
|
||||
- TESTS=group3
|
||||
- TESTS=group4
|
||||
- TESTS=adapter
|
||||
- TESTS=old
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
|
||||
before_script:
|
||||
- export MAVEN_SKIP_RC=true
|
||||
|
||||
install:
|
||||
- travis_wait 60 mvn install -Pdistribution -DskipTests=true -B -V -q
|
||||
|
||||
script:
|
||||
- ./travis-run-tests.sh $TESTS
|
||||
|
||||
sudo: false
|
||||
@@ -1,75 +1,35 @@
|
||||
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 SAML support in the works.
|
||||
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
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
Ensure you have JDK 8 (or newer), Maven 3.1.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 specified 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://www.keycloak.org/documentation.html) - 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
|
||||
* [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config
|
||||
* [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak
|
||||
|
||||
|
||||
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.
|
||||
@@ -1,145 +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>2.5.4.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>org.keycloak</groupId>
|
||||
<artifactId>keycloak-authz-client</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>
|
||||
-504
@@ -1,504 +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.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AdapterDeploymentContext {
|
||||
private static final Logger log = Logger.getLogger(AdapterDeploymentContext.class);
|
||||
protected KeycloakDeployment deployment;
|
||||
protected KeycloakConfigResolver configResolver;
|
||||
|
||||
public AdapterDeploymentContext() {
|
||||
}
|
||||
|
||||
/**
|
||||
* For single-tenant deployments, this constructor is to be used, as a
|
||||
* full KeycloakDeployment is known at deployment time and won't change
|
||||
* during the application deployment's life cycle.
|
||||
*
|
||||
* @param deployment A KeycloakConfigResolver, possibly missing the Auth
|
||||
* Server URL
|
||||
*/
|
||||
public AdapterDeploymentContext(KeycloakDeployment deployment) {
|
||||
this.deployment = deployment;
|
||||
}
|
||||
|
||||
/**
|
||||
* For multi-tenant deployments, this constructor is to be used, as a
|
||||
* KeycloakDeployment is not known at deployment time. It defers the
|
||||
* resolution of a KeycloakDeployment to a KeycloakConfigResolver,
|
||||
* to be implemented by the target application.
|
||||
*
|
||||
* @param configResolver A KeycloakConfigResolver that will be used
|
||||
* to resolve a KeycloakDeployment
|
||||
*/
|
||||
public AdapterDeploymentContext(KeycloakConfigResolver configResolver) {
|
||||
this.configResolver = configResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* For single-tenant deployments, it complements KeycloakDeployment
|
||||
* by resolving a relative Auth Server's URL based on the current request
|
||||
*
|
||||
* For multi-tenant deployments, defers the resolution of KeycloakDeployment
|
||||
* to the KeycloakConfigResolver .
|
||||
*
|
||||
* @param facade the Request/Response Façade , used to either determine
|
||||
* the Auth Server URL (single tenant) or pass thru to the
|
||||
* KeycloakConfigResolver.
|
||||
* @return
|
||||
*/
|
||||
public KeycloakDeployment resolveDeployment(HttpFacade facade) {
|
||||
if (null != configResolver) {
|
||||
return configResolver.resolve(facade.getRequest());
|
||||
}
|
||||
|
||||
if (deployment == null) return null;
|
||||
if (deployment.getAuthServerBaseUrl() == null) return deployment;
|
||||
|
||||
KeycloakDeployment resolvedDeployment = resolveUrls(deployment, facade);
|
||||
if (resolvedDeployment.getPublicKeyLocator() == null) {
|
||||
throw new RuntimeException("KeycloakDeployment was never initialized through appropriate SPIs");
|
||||
}
|
||||
return resolvedDeployment;
|
||||
}
|
||||
|
||||
protected KeycloakDeployment resolveUrls(KeycloakDeployment deployment, HttpFacade facade) {
|
||||
if (deployment.relativeUrls == RelativeUrlsUsed.NEVER) {
|
||||
// Absolute URI are already set to everything
|
||||
return deployment;
|
||||
} else {
|
||||
DeploymentDelegate delegate = new DeploymentDelegate(this.deployment);
|
||||
delegate.setAuthServerBaseUrl(getBaseBuilder(facade, this.deployment.getAuthServerBaseUrl()).build().toString());
|
||||
return delegate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This delegate is used to store temporary, per-request metadata like request resolved URLs.
|
||||
* Ever method is delegated except URL get methods and isConfigured()
|
||||
*
|
||||
*/
|
||||
protected static class DeploymentDelegate extends KeycloakDeployment {
|
||||
protected KeycloakDeployment delegate;
|
||||
|
||||
public DeploymentDelegate(KeycloakDeployment delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void setAuthServerBaseUrl(String authServerBaseUrl) {
|
||||
this.authServerBaseUrl = authServerBaseUrl;
|
||||
KeycloakUriBuilder serverBuilder = KeycloakUriBuilder.fromUri(authServerBaseUrl);
|
||||
resolveUrls(serverBuilder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelativeUrlsUsed getRelativeUrls() {
|
||||
return delegate.getRelativeUrls();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealmInfoUrl() {
|
||||
return (this.realmInfoUrl != null) ? this.realmInfoUrl : delegate.getRealmInfoUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenUrl() {
|
||||
return (this.tokenUrl != null) ? this.tokenUrl : delegate.getTokenUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeycloakUriBuilder getLogoutUrl() {
|
||||
return (this.logoutUrl != null) ? this.logoutUrl : delegate.getLogoutUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccountUrl() {
|
||||
return (this.accountUrl != null) ? this.accountUrl : delegate.getAccountUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRegisterNodeUrl() {
|
||||
return (this.registerNodeUrl != null) ? this.registerNodeUrl : delegate.getRegisterNodeUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnregisterNodeUrl() {
|
||||
return (this.unregisterNodeUrl != null) ? this.unregisterNodeUrl : delegate.getUnregisterNodeUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJwksUrl() {
|
||||
return (this.jwksUrl != null) ? this.jwksUrl : delegate.getJwksUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResourceName() {
|
||||
return delegate.getResourceName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRealm() {
|
||||
return delegate.getRealm();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRealm(String realm) {
|
||||
delegate.setRealm(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||
delegate.setPublicKeyLocator(publicKeyLocator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKeyLocator getPublicKeyLocator() {
|
||||
return delegate.getPublicKeyLocator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceName(String resourceName) {
|
||||
delegate.setResourceName(resourceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBearerOnly() {
|
||||
return delegate.isBearerOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBearerOnly(boolean bearerOnly) {
|
||||
delegate.setBearerOnly(bearerOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnableBasicAuth() {
|
||||
return delegate.isEnableBasicAuth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnableBasicAuth(boolean enableBasicAuth) {
|
||||
delegate.setEnableBasicAuth(enableBasicAuth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPublicClient() {
|
||||
return delegate.isPublicClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicClient(boolean publicClient) {
|
||||
delegate.setPublicClient(publicClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getResourceCredentials() {
|
||||
return delegate.getResourceCredentials();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceCredentials(Map<String, Object> resourceCredentials) {
|
||||
delegate.setResourceCredentials(resourceCredentials);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClientAuthenticator(ClientCredentialsProvider clientAuthenticator) {
|
||||
delegate.setClientAuthenticator(clientAuthenticator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientCredentialsProvider getClientAuthenticator() {
|
||||
return delegate.getClientAuthenticator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getClient() {
|
||||
return delegate.getClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClient(HttpClient client) {
|
||||
delegate.setClient(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getScope() {
|
||||
return delegate.getScope();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScope(String scope) {
|
||||
delegate.setScope(scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SslRequired getSslRequired() {
|
||||
return delegate.getSslRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSslRequired(SslRequired sslRequired) {
|
||||
delegate.setSslRequired(sslRequired);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenStore getTokenStore() {
|
||||
return delegate.getTokenStore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTokenStore(TokenStore tokenStore) {
|
||||
delegate.setTokenStore(tokenStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStateCookieName() {
|
||||
return delegate.getStateCookieName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateCookieName(String stateCookieName) {
|
||||
delegate.setStateCookieName(stateCookieName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseResourceRoleMappings() {
|
||||
return delegate.isUseResourceRoleMappings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseResourceRoleMappings(boolean useResourceRoleMappings) {
|
||||
delegate.setUseResourceRoleMappings(useResourceRoleMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCors() {
|
||||
return delegate.isCors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCors(boolean cors) {
|
||||
delegate.setCors(cors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCorsMaxAge() {
|
||||
return delegate.getCorsMaxAge();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCorsMaxAge(int corsMaxAge) {
|
||||
delegate.setCorsMaxAge(corsMaxAge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCorsAllowedHeaders() {
|
||||
return delegate.getCorsAllowedHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBefore(int notBefore) {
|
||||
delegate.setNotBefore(notBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNotBefore() {
|
||||
return delegate.getNotBefore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNotBefore(int notBefore) {
|
||||
delegate.setNotBefore(notBefore);
|
||||
getPublicKeyLocator().reset(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExposeToken(boolean exposeToken) {
|
||||
delegate.setExposeToken(exposeToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExposeToken() {
|
||||
return delegate.isExposeToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCorsAllowedMethods(String corsAllowedMethods) {
|
||||
delegate.setCorsAllowedMethods(corsAllowedMethods);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCorsAllowedMethods() {
|
||||
return delegate.getCorsAllowedMethods();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCorsAllowedHeaders(String corsAllowedHeaders) {
|
||||
delegate.setCorsAllowedHeaders(corsAllowedHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlwaysRefreshToken() {
|
||||
return delegate.isAlwaysRefreshToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlwaysRefreshToken(boolean alwaysRefreshToken) {
|
||||
delegate.setAlwaysRefreshToken(alwaysRefreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRegisterNodePeriod() {
|
||||
return delegate.getRegisterNodePeriod();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegisterNodePeriod(int registerNodePeriod) {
|
||||
delegate.setRegisterNodePeriod(registerNodePeriod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegisterNodeAtStartup(boolean registerNodeAtStartup) {
|
||||
delegate.setRegisterNodeAtStartup(registerNodeAtStartup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisterNodeAtStartup() {
|
||||
return delegate.isRegisterNodeAtStartup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrincipalAttribute() {
|
||||
return delegate.getPrincipalAttribute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrincipalAttribute(String principalAttribute) {
|
||||
delegate.setPrincipalAttribute(principalAttribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTurnOffChangeSessionIdOnLogin() {
|
||||
return delegate.isTurnOffChangeSessionIdOnLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTurnOffChangeSessionIdOnLogin(boolean turnOffChangeSessionIdOnLogin) {
|
||||
delegate.setTurnOffChangeSessionIdOnLogin(turnOffChangeSessionIdOnLogin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTokenMinimumTimeToLive() {
|
||||
return delegate.getTokenMinimumTimeToLive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||
delegate.setTokenMinimumTimeToLive(tokenMinimumTimeToLive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolicyEnforcer getPolicyEnforcer() {
|
||||
return delegate.getPolicyEnforcer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
delegate.setPolicyEnforcer(policyEnforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||
delegate.setMinTimeBetweenJwksRequests(minTimeBetweenJwksRequests);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinTimeBetweenJwksRequests() {
|
||||
return delegate.getMinTimeBetweenJwksRequests();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPublicKeyCacheTtl() {
|
||||
return delegate.getPublicKeyCacheTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
||||
delegate.setPublicKeyCacheTtl(publicKeyCacheTtl);
|
||||
}
|
||||
}
|
||||
|
||||
protected KeycloakUriBuilder getBaseBuilder(HttpFacade facade, String base) {
|
||||
KeycloakUriBuilder builder = KeycloakUriBuilder.fromUri(base);
|
||||
URI request = URI.create(facade.getRequest().getURI());
|
||||
String scheme = request.getScheme();
|
||||
if (deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
||||
scheme = "https";
|
||||
if (!request.getScheme().equals(scheme) && request.getPort() != -1) {
|
||||
log.error("request scheme: " + request.getScheme() + " ssl required");
|
||||
throw new RuntimeException("Can't resolve relative url from adapter config.");
|
||||
}
|
||||
}
|
||||
builder.scheme(scheme);
|
||||
builder.host(request.getHost());
|
||||
if (request.getPort() != -1) {
|
||||
builder.port(request.getPort());
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void close(HttpResponse response) {
|
||||
if (response.getEntity() != null) {
|
||||
try {
|
||||
response.getEntity().getContent().close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDeployment(AdapterConfig config) {
|
||||
if (null != configResolver) {
|
||||
throw new IllegalStateException("Cannot parse an adapter config and build an updated deployment when on a multi-tenant scenario.");
|
||||
}
|
||||
deployment = KeycloakDeploymentBuilder.build(config);
|
||||
}
|
||||
}
|
||||
@@ -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,92 +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.representations.AccessToken;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
-156
@@ -1,156 +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.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Pre-installed actions that must be authenticated
|
||||
*
|
||||
* Actions include:
|
||||
*
|
||||
* CORS Origin Check and Response headers
|
||||
* k_query_bearer_token: Get bearer token from server for Javascripts CORS requests
|
||||
*
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class AuthenticatedActionsHandler {
|
||||
private static final Logger log = Logger.getLogger(AuthenticatedActionsHandler.class);
|
||||
protected KeycloakDeployment deployment;
|
||||
protected OIDCHttpFacade facade;
|
||||
|
||||
public AuthenticatedActionsHandler(KeycloakDeployment deployment, OIDCHttpFacade facade) {
|
||||
this.deployment = deployment;
|
||||
this.facade = facade;
|
||||
}
|
||||
|
||||
public boolean handledRequest() {
|
||||
log.debugv("AuthenticatedActionsValve.invoke {0}", facade.getRequest().getURI());
|
||||
if (corsRequest()) return true;
|
||||
String requestUri = facade.getRequest().getURI();
|
||||
if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
|
||||
queryBearerToken();
|
||||
return true;
|
||||
}
|
||||
if (!isAuthorized()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void queryBearerToken() {
|
||||
log.debugv("queryBearerToken {0}",facade.getRequest().getURI());
|
||||
if (abortTokenResponse()) return;
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader("Content-Type", "text/plain");
|
||||
try {
|
||||
facade.getResponse().getOutputStream().write(facade.getSecurityContext().getTokenString().getBytes());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
facade.getResponse().end();
|
||||
}
|
||||
|
||||
protected boolean abortTokenResponse() {
|
||||
if (facade.getSecurityContext() == null) {
|
||||
log.debugv("Not logged in, sending back 401: {0}",facade.getRequest().getURI());
|
||||
facade.getResponse().sendError(401);
|
||||
facade.getResponse().end();
|
||||
return true;
|
||||
}
|
||||
if (!deployment.isExposeToken()) {
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().end();
|
||||
return true;
|
||||
}
|
||||
// Don't allow a CORS request if we're not validating CORS requests.
|
||||
if (!deployment.isCors() && facade.getRequest().getHeader(CorsHeaders.ORIGIN) != null) {
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().end();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean corsRequest() {
|
||||
if (!deployment.isCors()) return false;
|
||||
KeycloakSecurityContext securityContext = facade.getSecurityContext();
|
||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||
String requestOrigin = UriUtils.getOrigin(facade.getRequest().getURI());
|
||||
log.debugv("Origin: {0} uri: {1}", origin, facade.getRequest().getURI());
|
||||
if (securityContext != null && origin != null && !origin.equals(requestOrigin)) {
|
||||
AccessToken token = securityContext.getToken();
|
||||
Set<String> allowedOrigins = token.getAllowedOrigins();
|
||||
if (log.isDebugEnabled()) {
|
||||
for (String a : allowedOrigins) log.debug(" " + a);
|
||||
}
|
||||
if (allowedOrigins == null || (!allowedOrigins.contains("*") && !allowedOrigins.contains(origin))) {
|
||||
if (allowedOrigins == null) {
|
||||
log.debugv("allowedOrigins was null in token");
|
||||
} else {
|
||||
log.debugv("allowedOrigins did not contain origin");
|
||||
|
||||
}
|
||||
facade.getResponse().sendError(403);
|
||||
facade.getResponse().end();
|
||||
return true;
|
||||
}
|
||||
log.debugv("returning origin: {0}", origin);
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
} else {
|
||||
log.debugv("cors validation not needed as we're not a secure session or origin header was null: {0}", facade.getRequest().getURI());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAuthorized() {
|
||||
PolicyEnforcer policyEnforcer = this.deployment.getPolicyEnforcer();
|
||||
|
||||
if (policyEnforcer == null) {
|
||||
log.debugv("Policy enforcement is disabled.");
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
OIDCHttpFacade facade = (OIDCHttpFacade) this.facade;
|
||||
AuthorizationContext authorizationContext = policyEnforcer.enforce(facade);
|
||||
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) facade.getSecurityContext();
|
||||
|
||||
if (session != null) {
|
||||
session.setAuthorizationContext(authorizationContext);
|
||||
|
||||
return authorizationContext.isGranted();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to enforce policy decisions.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-126
@@ -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.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
-172
@@ -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.adapters.rotation.AdapterRSATokenVerifier;
|
||||
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 = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
} 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,107 +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.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class CookieTokenStore {
|
||||
|
||||
private static final Logger log = Logger.getLogger(CookieTokenStore.class);
|
||||
private static final String DELIM = "___";
|
||||
|
||||
public static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) {
|
||||
log.debugf("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
|
||||
String accessToken = session.getTokenString();
|
||||
String idToken = session.getIdTokenString();
|
||||
String refreshToken = session.getRefreshToken();
|
||||
String cookie = new StringBuilder(accessToken).append(DELIM)
|
||||
.append(idToken).append(DELIM)
|
||||
.append(refreshToken).toString();
|
||||
|
||||
String cookiePath = getContextPath(facade);
|
||||
facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
|
||||
}
|
||||
|
||||
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie(KeycloakDeployment deployment, HttpFacade facade, AdapterTokenStore tokenStore) {
|
||||
OIDCHttpFacade.Cookie cookie = facade.getRequest().getCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
|
||||
if (cookie == null) {
|
||||
log.debug("Not found adapter state cookie in current request");
|
||||
return null;
|
||||
}
|
||||
|
||||
String cookieVal = cookie.getValue();
|
||||
|
||||
String[] tokens = cookieVal.split(DELIM);
|
||||
if (tokens.length != 3) {
|
||||
log.warnf("Invalid format of %s cookie. Count of tokens: %s, expected 3", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, tokens.length);
|
||||
return null;
|
||||
}
|
||||
|
||||
String accessTokenString = tokens[0];
|
||||
String idTokenString = tokens[1];
|
||||
String refreshTokenString = tokens[2];
|
||||
|
||||
try {
|
||||
// Skip check if token is active now. It's supposed to be done later by the caller
|
||||
AccessToken accessToken = AdapterRSATokenVerifier.verifyToken(accessTokenString, deployment, false, true);
|
||||
IDToken idToken;
|
||||
if (idTokenString != null && idTokenString.length() > 0) {
|
||||
try {
|
||||
JWSInput input = new JWSInput(idTokenString);
|
||||
idToken = input.readJsonContent(IDToken.class);
|
||||
} catch (JWSInputException e) {
|
||||
throw new VerificationException(e);
|
||||
}
|
||||
} else {
|
||||
idToken = null;
|
||||
}
|
||||
|
||||
log.debug("Token Verification succeeded!");
|
||||
RefreshableKeycloakSecurityContext secContext = new RefreshableKeycloakSecurityContext(deployment, tokenStore, accessTokenString, accessToken, idTokenString, idToken, refreshTokenString);
|
||||
return new KeycloakPrincipal<RefreshableKeycloakSecurityContext>(AdapterUtils.getPrincipalName(deployment, accessToken), secContext);
|
||||
} catch (VerificationException ve) {
|
||||
log.warn("Failed verify token", ve);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeCookie(HttpFacade facade) {
|
||||
String cookiePath = getContextPath(facade);
|
||||
facade.getResponse().resetCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookiePath);
|
||||
}
|
||||
|
||||
private static String getContextPath(HttpFacade facade) {
|
||||
String uri = facade.getRequest().getURI();
|
||||
String path = KeycloakUriBuilder.fromUri(uri).getPath();
|
||||
int index = path.indexOf("/", 1);
|
||||
return index == -1 ? path : path.substring(0, index);
|
||||
}
|
||||
}
|
||||
@@ -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,72 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpRequestBase;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HttpAdapterUtils {
|
||||
|
||||
|
||||
public static <T> T sendJsonHttpRequest(KeycloakDeployment deployment, HttpRequestBase httpRequest, Class<T> clazz) throws HttpClientAdapterException {
|
||||
try {
|
||||
HttpResponse response = deployment.getClient().execute(httpRequest);
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status != 200) {
|
||||
close(response);
|
||||
throw new HttpClientAdapterException("Unexpected status = " + status);
|
||||
}
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
throw new HttpClientAdapterException("There was no entity.");
|
||||
}
|
||||
InputStream is = entity.getContent();
|
||||
try {
|
||||
return JsonSerialization.readValue(is, clazz);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new HttpClientAdapterException("IO error", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void close(HttpResponse response) {
|
||||
if (response.getEntity() != null) {
|
||||
try {
|
||||
response.getEntity().getContent().close();
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HttpClientAdapterException extends Exception {
|
||||
|
||||
public HttpClientAdapterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public HttpClientAdapterException(String message, Throwable t) {
|
||||
super(message, t);
|
||||
}
|
||||
}
|
||||
-397
@@ -1,397 +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.client.CookieStore;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.conn.ClientConnectionManager;
|
||||
import org.apache.http.conn.params.ConnRoutePNames;
|
||||
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.common.util.EnvUtil;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.representations.adapters.config.AdapterHttpClientConfig;
|
||||
|
||||
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.net.URI;
|
||||
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;
|
||||
protected HttpHost proxyHost;
|
||||
|
||||
|
||||
/**
|
||||
* 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 (proxyHost != null) {
|
||||
params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxyHost);
|
||||
}
|
||||
|
||||
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(AdapterHttpClientConfig 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);
|
||||
}
|
||||
|
||||
configureProxyForAuthServerIfProvided(adapterConfig);
|
||||
|
||||
return build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a the proxy to use for auth-server requests if provided.
|
||||
* <p>
|
||||
* If the given {@link AdapterHttpClientConfig} contains the attribute {@code proxy-url} we use the
|
||||
* given URL as a proxy server, otherwise the proxy configuration is ignored.
|
||||
* </p>
|
||||
*
|
||||
* @param adapterConfig
|
||||
*/
|
||||
private void configureProxyForAuthServerIfProvided(AdapterHttpClientConfig adapterConfig) {
|
||||
|
||||
if (adapterConfig == null || adapterConfig.getProxyUrl() == null || adapterConfig.getProxyUrl().trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
URI uri = URI.create(adapterConfig.getProxyUrl());
|
||||
this.proxyHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
|
||||
/**
|
||||
* On multi-tenant scenarios, Keycloak will defer the resolution of a
|
||||
* KeycloakDeployment to the target application at the request-phase.
|
||||
*
|
||||
* A Request object is passed to the resolver and callers expect a complete
|
||||
* KeycloakDeployment. Based on this KeycloakDeployment, Keycloak will resume
|
||||
* authenticating and authorizing the request.
|
||||
*
|
||||
* The easiest way to build a KeycloakDeployment is to use
|
||||
* KeycloakDeploymentBuilder , passing the InputStream of an existing
|
||||
* keycloak.json to the build() method.
|
||||
*
|
||||
* @see KeycloakDeploymentBuilder
|
||||
* @author Juraci Paixão Kröhling <juraci at kroehling.de>
|
||||
*/
|
||||
public interface KeycloakConfigResolver {
|
||||
|
||||
public KeycloakDeployment resolve(Request facade);
|
||||
|
||||
}
|
||||
-417
@@ -1,417 +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.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.PublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
|
||||
import java.net.URI;
|
||||
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 PublicKeyLocator publicKeyLocator;
|
||||
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 jwksUrl;
|
||||
protected String principalAttribute = "sub";
|
||||
|
||||
protected String resourceName;
|
||||
protected boolean bearerOnly;
|
||||
protected boolean autodetectBearerOnly;
|
||||
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;
|
||||
protected int tokenMinimumTimeToLive;
|
||||
protected int minTimeBetweenJwksRequests;
|
||||
protected int publicKeyCacheTtl;
|
||||
private PolicyEnforcer policyEnforcer;
|
||||
|
||||
public KeycloakDeployment() {
|
||||
}
|
||||
|
||||
public boolean isConfigured() {
|
||||
return getRealm() != null && getPublicKeyLocator() != null && (isBearerOnly() || getAuthServerBaseUrl() != null);
|
||||
}
|
||||
|
||||
public String getResourceName() {
|
||||
return resourceName;
|
||||
}
|
||||
|
||||
public String getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm) {
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public PublicKeyLocator getPublicKeyLocator() {
|
||||
return publicKeyLocator;
|
||||
}
|
||||
|
||||
public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
|
||||
this.publicKeyLocator = publicKeyLocator;
|
||||
}
|
||||
|
||||
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();
|
||||
jwksUrl = authUrlBuilder.clone().path(ServiceUrlConstants.JWKS_URL).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 String getJwksUrl() {
|
||||
return jwksUrl;
|
||||
}
|
||||
|
||||
public void setResourceName(String resourceName) {
|
||||
this.resourceName = resourceName;
|
||||
}
|
||||
|
||||
public boolean isBearerOnly() {
|
||||
return bearerOnly;
|
||||
}
|
||||
|
||||
public void setBearerOnly(boolean bearerOnly) {
|
||||
this.bearerOnly = bearerOnly;
|
||||
}
|
||||
|
||||
public boolean isAutodetectBearerOnly() {
|
||||
return autodetectBearerOnly;
|
||||
}
|
||||
|
||||
public void setAutodetectBearerOnly(boolean autodetectBearerOnly) {
|
||||
this.autodetectBearerOnly = autodetectBearerOnly;
|
||||
}
|
||||
|
||||
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 void updateNotBefore(int notBefore) {
|
||||
this.notBefore = notBefore;
|
||||
getPublicKeyLocator().reset(this);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public int getTokenMinimumTimeToLive() {
|
||||
return tokenMinimumTimeToLive;
|
||||
}
|
||||
|
||||
public void setTokenMinimumTimeToLive(final int tokenMinimumTimeToLive) {
|
||||
this.tokenMinimumTimeToLive = tokenMinimumTimeToLive;
|
||||
}
|
||||
|
||||
public int getMinTimeBetweenJwksRequests() {
|
||||
return minTimeBetweenJwksRequests;
|
||||
}
|
||||
|
||||
public void setMinTimeBetweenJwksRequests(int minTimeBetweenJwksRequests) {
|
||||
this.minTimeBetweenJwksRequests = minTimeBetweenJwksRequests;
|
||||
}
|
||||
|
||||
public int getPublicKeyCacheTtl() {
|
||||
return publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
|
||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
}
|
||||
|
||||
public PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
}
|
||||
-158
@@ -1,158 +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.adapters.authorization.PolicyEnforcer;
|
||||
import org.keycloak.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
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);
|
||||
HardcodedPublicKeyLocator pkLocator = new HardcodedPublicKeyLocator(realmKey);
|
||||
deployment.setPublicKeyLocator(pkLocator);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
JWKPublicKeyLocator pkLocator = new JWKPublicKeyLocator();
|
||||
deployment.setPublicKeyLocator(pkLocator);
|
||||
}
|
||||
|
||||
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.setAutodetectBearerOnly(adapterConfig.isAutodetectBearerOnly());
|
||||
deployment.setEnableBasicAuth(adapterConfig.isEnableBasicAuth());
|
||||
deployment.setAlwaysRefreshToken(adapterConfig.isAlwaysRefreshToken());
|
||||
deployment.setRegisterNodeAtStartup(adapterConfig.isRegisterNodeAtStartup());
|
||||
deployment.setRegisterNodePeriod(adapterConfig.getRegisterNodePeriod());
|
||||
deployment.setTokenMinimumTimeToLive(adapterConfig.getTokenMinimumTimeToLive());
|
||||
deployment.setMinTimeBetweenJwksRequests(adapterConfig.getMinTimeBetweenJwksRequests());
|
||||
deployment.setPublicKeyCacheTtl(adapterConfig.getPublicKeyCacheTtl());
|
||||
|
||||
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-server-url");
|
||||
}
|
||||
deployment.setAuthServerBaseUrl(adapterConfig);
|
||||
if (adapterConfig.getTurnOffChangeSessionIdOnLogin() != null) {
|
||||
deployment.setTurnOffChangeSessionIdOnLogin(adapterConfig.getTurnOffChangeSessionIdOnLogin());
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig policyEnforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
|
||||
if (policyEnforcerConfig != null) {
|
||||
deployment.setPolicyEnforcer(new PolicyEnforcer(deployment, adapterConfig));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-134
@@ -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.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
import org.keycloak.common.util.Time;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class NodesRegistrationManagement {
|
||||
|
||||
private static final Logger log = Logger.getLogger(NodesRegistrationManagement.class);
|
||||
|
||||
private final Map<String, NodeRegistrationContext> nodeRegistrations = new ConcurrentHashMap<String, NodeRegistrationContext>();
|
||||
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// Sending registration event during first request to application or if re-registration is needed
|
||||
public void tryRegister(final KeycloakDeployment resolvedDeployment) {
|
||||
if (resolvedDeployment.isRegisterNodeAtStartup()) {
|
||||
final String registrationUri = resolvedDeployment.getRegisterNodeUrl();
|
||||
if (needRefreshRegistration(registrationUri, resolvedDeployment)) {
|
||||
Runnable runnable = new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// Need to check it again in case that executor triggered by other thread already finished computation in the meantime
|
||||
if (needRefreshRegistration(registrationUri, resolvedDeployment)) {
|
||||
sendRegistrationEvent(resolvedDeployment);
|
||||
}
|
||||
}
|
||||
};
|
||||
executor.execute(runnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needRefreshRegistration(String registrationUri, KeycloakDeployment resolvedDeployment) {
|
||||
NodeRegistrationContext currentRegistration = nodeRegistrations.get(registrationUri);
|
||||
/// We don't yet have any registration for this node
|
||||
if (currentRegistration == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return currentRegistration.lastRegistrationTime + resolvedDeployment.getRegisterNodePeriod() < Time.currentTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called during undeployment or server stop. De-register from all previously registered deployments
|
||||
*/
|
||||
public void stop() {
|
||||
Collection<NodeRegistrationContext> allRegistrations = nodeRegistrations.values();
|
||||
for (NodeRegistrationContext registration : allRegistrations) {
|
||||
sendUnregistrationEvent(registration.resolvedDeployment);
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendRegistrationEvent(KeycloakDeployment deployment) {
|
||||
log.debug("Sending registration event right now");
|
||||
|
||||
String host = HostUtils.getHostName();
|
||||
try {
|
||||
ServerRequest.invokeRegisterNode(deployment, host);
|
||||
NodeRegistrationContext regContext = new NodeRegistrationContext(Time.currentTime(), deployment);
|
||||
nodeRegistrations.put(deployment.getRegisterNodeUrl(), regContext);
|
||||
log.debugf("Node '%s' successfully registered in Keycloak", host);
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
log.error("failed to register node to keycloak");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
if (failure.getError() != null) {
|
||||
log.error(" " + failure.getError());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("failed to register node to keycloak", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean sendUnregistrationEvent(KeycloakDeployment deployment) {
|
||||
log.debug("Sending Unregistration event right now");
|
||||
|
||||
String host = HostUtils.getHostName();
|
||||
try {
|
||||
ServerRequest.invokeUnregisterNode(deployment, host);
|
||||
log.debugf("Node '%s' successfully unregistered from Keycloak", host);
|
||||
return true;
|
||||
} catch (ServerRequest.HttpFailure failure) {
|
||||
log.error("failed to unregister node from keycloak");
|
||||
log.error("status from server: " + failure.getStatus());
|
||||
if (failure.getError() != null) {
|
||||
log.error(" " + failure.getError());
|
||||
}
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
log.error("failed to unregister node from keycloak", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NodeRegistrationContext {
|
||||
|
||||
private final Integer lastRegistrationTime;
|
||||
// deployment instance used for registration request
|
||||
private final KeycloakDeployment resolvedDeployment;
|
||||
|
||||
public NodeRegistrationContext(Integer lastRegTime, KeycloakDeployment deployment) {
|
||||
this.lastRegistrationTime = lastRegTime;
|
||||
this.resolvedDeployment = deployment;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-380
@@ -1,380 +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.adapters.rotation.AdapterRSATokenVerifier;
|
||||
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.common.util.Encode;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.UriUtils;
|
||||
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.util.TokenUtil;
|
||||
|
||||
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);
|
||||
|
||||
String prompt = getQueryParamValue(OAuth2Constants.PROMPT);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.PROMPT);
|
||||
|
||||
String maxAge = getQueryParamValue(OAuth2Constants.MAX_AGE);
|
||||
url = UriUtils.stripQueryParam(url, OAuth2Constants.MAX_AGE);
|
||||
|
||||
KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone()
|
||||
.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE)
|
||||
.queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName())
|
||||
.queryParam(OAuth2Constants.REDIRECT_URI, Encode.encodeQueryParamAsIs(url)) // Need to encode uri ourselves as queryParam() will not encode % characters.
|
||||
.queryParam(OAuth2Constants.STATE, state)
|
||||
.queryParam("login", "true");
|
||||
if(loginHint != null && loginHint.length() > 0){
|
||||
redirectUriBuilder.queryParam("login_hint",loginHint);
|
||||
}
|
||||
if (idpHint != null && idpHint.length() > 0) {
|
||||
redirectUriBuilder.queryParam(AdapterConstants.KC_IDP_HINT,idpHint);
|
||||
}
|
||||
if (prompt != null && prompt.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.PROMPT, prompt);
|
||||
}
|
||||
if (maxAge != null && maxAge.length() > 0) {
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.MAX_AGE, maxAge);
|
||||
}
|
||||
|
||||
scope = TokenUtil.attachOIDCScope(scope);
|
||||
redirectUriBuilder.queryParam(OAuth2Constants.SCOPE, scope);
|
||||
|
||||
return redirectUriBuilder.build().toString();
|
||||
}
|
||||
|
||||
protected int sslRedirectPort() {
|
||||
return sslRedirectPort;
|
||||
}
|
||||
|
||||
protected String getStateCode() {
|
||||
return 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 = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
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.updateNotBefore(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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-64
@@ -1,64 +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,
|
||||
NO_QUERY_PARAMETER_ACCESS_TOKEN
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
-29
@@ -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();
|
||||
}
|
||||
-282
@@ -1,282 +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 java.security.PublicKey;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.authentication.ClientCredentialsProvider;
|
||||
import org.keycloak.adapters.authentication.JWTClientCredentialsProvider;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.adapters.spi.UserSessionManagement;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.JWSInputException;
|
||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||
import org.keycloak.representations.VersionRepresentation;
|
||||
import org.keycloak.representations.adapters.action.AdminAction;
|
||||
import org.keycloak.representations.adapters.action.LogoutAction;
|
||||
import org.keycloak.representations.adapters.action.PushNotBeforeAction;
|
||||
import org.keycloak.representations.adapters.action.TestAvailabilityAction;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PreAuthActionsHandler {
|
||||
|
||||
private static final Logger log = Logger.getLogger(PreAuthActionsHandler.class);
|
||||
|
||||
protected UserSessionManagement userSessionManagement;
|
||||
protected AdapterDeploymentContext deploymentContext;
|
||||
protected KeycloakDeployment deployment;
|
||||
protected HttpFacade facade;
|
||||
|
||||
public PreAuthActionsHandler(UserSessionManagement userSessionManagement, AdapterDeploymentContext deploymentContext, HttpFacade facade) {
|
||||
this.userSessionManagement = userSessionManagement;
|
||||
this.deploymentContext = deploymentContext;
|
||||
this.facade = facade;
|
||||
}
|
||||
|
||||
protected boolean resolveDeployment() {
|
||||
deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (!deployment.isConfigured()) {
|
||||
log.warn("can't take request, adapter not configured");
|
||||
facade.getResponse().sendError(403, "adapter not configured");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleRequest() {
|
||||
String requestUri = facade.getRequest().getURI();
|
||||
log.debugv("adminRequest {0}", requestUri);
|
||||
if (preflightCors()) {
|
||||
return true;
|
||||
}
|
||||
if (requestUri.endsWith(AdapterConstants.K_LOGOUT)) {
|
||||
if (!resolveDeployment()) return true;
|
||||
handleLogout();
|
||||
return true;
|
||||
} else if (requestUri.endsWith(AdapterConstants.K_PUSH_NOT_BEFORE)) {
|
||||
if (!resolveDeployment()) return true;
|
||||
handlePushNotBefore();
|
||||
return true;
|
||||
} else if (requestUri.endsWith(AdapterConstants.K_VERSION)) {
|
||||
handleVersion();
|
||||
return true;
|
||||
} else if (requestUri.endsWith(AdapterConstants.K_TEST_AVAILABLE)) {
|
||||
if (!resolveDeployment()) return true;
|
||||
handleTestAvailable();
|
||||
return true;
|
||||
} else if (requestUri.endsWith(AdapterConstants.K_JWKS)) {
|
||||
if (!resolveDeployment()) return true;
|
||||
handleJwksRequest();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean preflightCors() {
|
||||
// don't need to resolve deployment on cors requests. Just need to know local cors config.
|
||||
KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
|
||||
if (!deployment.isCors()) return false;
|
||||
log.debugv("checkCorsPreflight {0}", facade.getRequest().getURI());
|
||||
if (!facade.getRequest().getMethod().equalsIgnoreCase("OPTIONS")) {
|
||||
return false;
|
||||
}
|
||||
if (facade.getRequest().getHeader(CorsHeaders.ORIGIN) == null) {
|
||||
log.debug("checkCorsPreflight: no origin header");
|
||||
return false;
|
||||
}
|
||||
log.debug("Preflight request returning");
|
||||
facade.getResponse().setStatus(200);
|
||||
String origin = facade.getRequest().getHeader(CorsHeaders.ORIGIN);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
String requestMethods = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_METHOD);
|
||||
if (requestMethods != null) {
|
||||
if (deployment.getCorsAllowedMethods() != null) {
|
||||
requestMethods = deployment.getCorsAllowedMethods();
|
||||
}
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethods);
|
||||
}
|
||||
String allowHeaders = facade.getRequest().getHeader(CorsHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
|
||||
if (allowHeaders != null) {
|
||||
if (deployment.getCorsAllowedHeaders() != null) {
|
||||
allowHeaders = deployment.getCorsAllowedHeaders();
|
||||
}
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_ALLOW_HEADERS, allowHeaders);
|
||||
}
|
||||
if (deployment.getCorsMaxAge() > -1) {
|
||||
facade.getResponse().setHeader(CorsHeaders.ACCESS_CONTROL_MAX_AGE, Integer.toString(deployment.getCorsMaxAge()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleLogout() {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("K_LOGOUT sent");
|
||||
}
|
||||
try {
|
||||
JWSInput token = verifyAdminRequest();
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
LogoutAction action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
|
||||
if (!validateAction(action)) return;
|
||||
if (action.getAdapterSessionIds() != null) {
|
||||
userSessionManagement.logoutHttpSessions(action.getAdapterSessionIds());
|
||||
} else {
|
||||
log.debugf("logout of all sessions for application '%s'", action.getResource());
|
||||
if (action.getNotBefore() > deployment.getNotBefore()) {
|
||||
deployment.updateNotBefore(action.getNotBefore());
|
||||
}
|
||||
userSessionManagement.logoutAll();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void handlePushNotBefore() {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("K_PUSH_NOT_BEFORE sent");
|
||||
}
|
||||
try {
|
||||
JWSInput token = verifyAdminRequest();
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
PushNotBeforeAction action = JsonSerialization.readValue(token.getContent(), PushNotBeforeAction.class);
|
||||
if (!validateAction(action)) return;
|
||||
deployment.updateNotBefore(action.getNotBefore());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleTestAvailable() {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("K_TEST_AVAILABLE sent");
|
||||
}
|
||||
try {
|
||||
JWSInput token = verifyAdminRequest();
|
||||
if (token == null) {
|
||||
return;
|
||||
}
|
||||
TestAvailabilityAction action = JsonSerialization.readValue(token.getContent(), TestAvailabilityAction.class);
|
||||
validateAction(action);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected JWSInput verifyAdminRequest() throws Exception {
|
||||
if (!facade.getRequest().isSecure() && deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr())) {
|
||||
log.warn("SSL is required for adapter admin action");
|
||||
facade.getResponse().sendError(403, "ssl required");
|
||||
return null;
|
||||
}
|
||||
String token = StreamUtil.readString(facade.getRequest().getInputStream());
|
||||
if (token == null) {
|
||||
log.warn("admin request failed, no token");
|
||||
facade.getResponse().sendError(403, "no token");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JWSInput input = new JWSInput(token);
|
||||
PublicKey publicKey = AdapterRSATokenVerifier.getPublicKey(input.getHeader().getKeyId(), deployment);
|
||||
if (RSAProvider.verify(input, publicKey)) {
|
||||
return input;
|
||||
}
|
||||
} catch (JWSInputException ignore) {
|
||||
}
|
||||
|
||||
log.warn("admin request failed, unable to verify token");
|
||||
facade.getResponse().sendError(403, "no token");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected boolean validateAction(AdminAction action) {
|
||||
if (!action.validate()) {
|
||||
log.warn("admin request failed, not validated" + action.getAction());
|
||||
facade.getResponse().sendError(400, "Not validated");
|
||||
return false;
|
||||
}
|
||||
if (action.isExpired()) {
|
||||
log.warn("admin request failed, expired token");
|
||||
facade.getResponse().sendError(400, "Expired token");
|
||||
return false;
|
||||
}
|
||||
if (!deployment.getResourceName().equals(action.getResource())) {
|
||||
log.warn("Resource name does not match");
|
||||
facade.getResponse().sendError(400, "Resource name does not match");
|
||||
return false;
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void handleVersion() {
|
||||
try {
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader("Content-Type", "application/json");
|
||||
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), VersionRepresentation.SINGLETON);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleJwksRequest() {
|
||||
try {
|
||||
JSONWebKeySet jwks = new JSONWebKeySet();
|
||||
ClientCredentialsProvider clientCredentialsProvider = deployment.getClientAuthenticator();
|
||||
|
||||
// For now, just get signature key from JWT provider. We can add more if we support encryption etc.
|
||||
if (clientCredentialsProvider instanceof JWTClientCredentialsProvider) {
|
||||
PublicKey publicKey = ((JWTClientCredentialsProvider) clientCredentialsProvider).getPublicKey();
|
||||
JWK jwk = JWKBuilder.create().rs256(publicKey);
|
||||
jwks.setKeys(new JWK[] { jwk });
|
||||
} else {
|
||||
jwks.setKeys(new JWK[] {});
|
||||
}
|
||||
|
||||
facade.getResponse().setStatus(200);
|
||||
facade.getResponse().setHeader("Content-Type", "application/json");
|
||||
JsonSerialization.writeValueToStream(facade.getResponse().getOutputStream(), jwks);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -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.adapters;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:froehlich.ch@gmail.com">Christian Froehlich</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class QueryParamterTokenRequestAuthenticator extends BearerTokenRequestAuthenticator {
|
||||
public static final String ACCESS_TOKEN = "access_token";
|
||||
protected Logger log = Logger.getLogger(QueryParamterTokenRequestAuthenticator.class);
|
||||
|
||||
public QueryParamterTokenRequestAuthenticator(KeycloakDeployment deployment) {
|
||||
super(deployment);
|
||||
}
|
||||
|
||||
public AuthOutcome authenticate(HttpFacade exchange) {
|
||||
tokenString = null;
|
||||
tokenString = getAccessTokenFromQueryParamter(exchange);
|
||||
if (tokenString == null || tokenString.trim().isEmpty()) {
|
||||
challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_QUERY_PARAMETER_ACCESS_TOKEN, null, null);
|
||||
return AuthOutcome.NOT_ATTEMPTED;
|
||||
}
|
||||
return (authenticateToken(exchange, tokenString));
|
||||
}
|
||||
|
||||
String getAccessTokenFromQueryParamter(HttpFacade exchange) {
|
||||
try {
|
||||
if (exchange != null && exchange.getRequest() != null) {
|
||||
return exchange.getRequest().getQueryParamValue(ACCESS_TOKEN);
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-165
@@ -1,165 +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.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.IDToken;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class RefreshableKeycloakSecurityContext extends KeycloakSecurityContext {
|
||||
|
||||
protected static Logger log = Logger.getLogger(RefreshableKeycloakSecurityContext.class);
|
||||
|
||||
protected transient KeycloakDeployment deployment;
|
||||
protected transient AdapterTokenStore tokenStore;
|
||||
protected String refreshToken;
|
||||
|
||||
public RefreshableKeycloakSecurityContext() {
|
||||
}
|
||||
|
||||
public RefreshableKeycloakSecurityContext(KeycloakDeployment deployment, AdapterTokenStore tokenStore, String tokenString, AccessToken token, String idTokenString, IDToken idToken, String refreshToken) {
|
||||
super(tokenString, token, idTokenString, idToken);
|
||||
this.deployment = deployment;
|
||||
this.tokenStore = tokenStore;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getToken() {
|
||||
refreshExpiredToken(true);
|
||||
return super.getToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenString() {
|
||||
refreshExpiredToken(true);
|
||||
return super.getTokenString();
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
public void logout(KeycloakDeployment deployment) {
|
||||
try {
|
||||
ServerRequest.invokeLogout(deployment, refreshToken);
|
||||
} catch (Exception e) {
|
||||
log.error("failed to invoke remote logout", e);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return token != null && this.token.isActive() && deployment!=null && this.token.getIssuedAt() > deployment.getNotBefore();
|
||||
}
|
||||
|
||||
public boolean isTokenTimeToLiveSufficient(AccessToken token) {
|
||||
return token != null && (token.getExpiration() - this.deployment.getTokenMinimumTimeToLive()) > Time.currentTime();
|
||||
}
|
||||
|
||||
public KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
public void setCurrentRequestInfo(KeycloakDeployment deployment, AdapterTokenStore tokenStore) {
|
||||
this.deployment = deployment;
|
||||
this.tokenStore = tokenStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param checkActive if true, then we won't send refresh request if current accessToken is still active.
|
||||
* @return true if accessToken is active or was successfully refreshed
|
||||
*/
|
||||
public boolean refreshExpiredToken(boolean checkActive) {
|
||||
if (checkActive) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("checking whether to refresh.");
|
||||
}
|
||||
if (isActive() && isTokenTimeToLiveSufficient(this.token)) return true;
|
||||
}
|
||||
|
||||
if (this.deployment == null || refreshToken == null) return false; // Might be serialized in HttpSession?
|
||||
|
||||
if (!this.getRealm().equals(this.deployment.getRealm())) {
|
||||
// this should not happen, but let's check it anyway
|
||||
return false;
|
||||
}
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Doing refresh");
|
||||
}
|
||||
AccessTokenResponse response = null;
|
||||
try {
|
||||
response = ServerRequest.invokeRefresh(deployment, refreshToken);
|
||||
} catch (IOException e) {
|
||||
log.error("Refresh token failure", e);
|
||||
return false;
|
||||
} catch (ServerRequest.HttpFailure httpFailure) {
|
||||
log.error("Refresh token failure status: " + httpFailure.getStatus() + " " + httpFailure.getError());
|
||||
return false;
|
||||
}
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("received refresh response");
|
||||
}
|
||||
String tokenString = response.getToken();
|
||||
AccessToken token = null;
|
||||
try {
|
||||
token = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
log.debug("Token Verification succeeded!");
|
||||
} catch (VerificationException e) {
|
||||
log.error("failed verification of token");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the TTL is greater-or-equal to the expire time on the refreshed token, have to abort or go into an infinite refresh loop
|
||||
if (!isTokenTimeToLiveSufficient(token)) {
|
||||
log.error("failed to refresh the token with a longer time-to-live than the minimum");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.getNotBeforePolicy() > deployment.getNotBefore()) {
|
||||
deployment.updateNotBefore(response.getNotBeforePolicy());
|
||||
}
|
||||
|
||||
this.token = token;
|
||||
if (response.getRefreshToken() != null) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Setup new refresh token to the security context");
|
||||
}
|
||||
this.refreshToken = response.getRefreshToken();
|
||||
}
|
||||
this.tokenString = tokenString;
|
||||
tokenStore.refreshCallback(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setAuthorizationContext(AuthorizationContext authorizationContext) {
|
||||
this.authorizationContext = authorizationContext;
|
||||
}
|
||||
}
|
||||
-239
@@ -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 java.util.Collections;
|
||||
import java.util.List;
|
||||
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;
|
||||
}
|
||||
|
||||
QueryParamterTokenRequestAuthenticator queryParamAuth = createQueryParamterTokenRequestAuthenticator();
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("try query paramter auth");
|
||||
}
|
||||
|
||||
outcome = queryParamAuth.authenticate(facade);
|
||||
if (outcome == AuthOutcome.FAILED) {
|
||||
challenge = queryParamAuth.getChallenge();
|
||||
log.debug("QueryParamAuth auth FAILED");
|
||||
return AuthOutcome.FAILED;
|
||||
} else if (outcome == AuthOutcome.AUTHENTICATED) {
|
||||
if (verifySSL()) return AuthOutcome.FAILED;
|
||||
log.debug("QueryParamAuth AUTHENTICATED");
|
||||
completeAuthentication(queryParamAuth, "KEYCLOAK");
|
||||
return AuthOutcome.AUTHENTICATED;
|
||||
}
|
||||
|
||||
if (deployment.isEnableBasicAuth()) {
|
||||
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) {
|
||||
if (verifySSL()) return AuthOutcome.FAILED;
|
||||
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 (isAutodetectedBearerOnly(facade.getRequest())) {
|
||||
challenge = bearer.getChallenge();
|
||||
log.debug("NOT_ATTEMPTED: Treating as 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 boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
|
||||
if (!deployment.isAutodetectBearerOnly()) return false;
|
||||
|
||||
String headerValue = facade.getRequest().getHeader("X-Requested-With");
|
||||
if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
headerValue = facade.getRequest().getHeader("Faces-Request");
|
||||
if (headerValue != null && headerValue.startsWith("partial/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
headerValue = facade.getRequest().getHeader("SOAPAction");
|
||||
if (headerValue != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<String> accepts = facade.getRequest().getHeaders("Accept");
|
||||
if (accepts == null) accepts = Collections.emptyList();
|
||||
|
||||
for (String accept : accepts) {
|
||||
if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract OAuthRequestAuthenticator createOAuthAuthenticator();
|
||||
|
||||
protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
|
||||
return new BearerTokenRequestAuthenticator(deployment);
|
||||
}
|
||||
|
||||
protected BasicAuthRequestAuthenticator createBasicAuthAuthenticator() {
|
||||
return new BasicAuthRequestAuthenticator(deployment);
|
||||
}
|
||||
|
||||
protected QueryParamterTokenRequestAuthenticator createQueryParamterTokenRequestAuthenticator() {
|
||||
return new QueryParamterTokenRequestAuthenticator(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.common.util.HostUtils;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.common.util.StreamUtil;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-134
@@ -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;
|
||||
}
|
||||
}
|
||||
-78
@@ -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 org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
-116
@@ -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 org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceConfigurationError;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* @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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-66
@@ -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 org.jboss.logging.Logger;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.util.BasicAuthHelper;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-159
@@ -1,159 +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.KeyPair;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.common.util.KeystoreUtil;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jws.JWSBuilder;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Client authentication based on JWT signed by client private key .
|
||||
* 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 KeyPair keyPair;
|
||||
private JWK publicKeyJwk;
|
||||
|
||||
private int tokenTimeout;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
public void setupKeyPair(KeyPair keyPair) {
|
||||
this.keyPair = keyPair;
|
||||
this.publicKeyJwk = JWKBuilder.create().rs256(keyPair.getPublic());
|
||||
}
|
||||
|
||||
public void setTokenTimeout(int tokenTimeout) {
|
||||
this.tokenTimeout = tokenTimeout;
|
||||
}
|
||||
|
||||
protected int getTokenTimeout() {
|
||||
return tokenTimeout;
|
||||
}
|
||||
|
||||
public PublicKey getPublicKey() {
|
||||
return keyPair.getPublic();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
KeyPair keyPair = KeystoreUtil.loadKeyPairFromKeystore(clientKeystoreFile, clientKeystorePassword, clientKeyPassword, clientKeyAlias, clientKeystoreFormat);
|
||||
setupKeyPair(keyPair);
|
||||
|
||||
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()
|
||||
.kid(publicKeyJwk.getKeyId())
|
||||
.jsonContent(jwt)
|
||||
.rsa256(keyPair.getPrivate());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
-294
@@ -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.adapters.authorization;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Request;
|
||||
import org.keycloak.adapters.spi.HttpFacade.Response;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public abstract class AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(AbstractPolicyEnforcer.class);
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final PolicyEnforcer policyEnforcer;
|
||||
|
||||
private Map<String, PathConfig> paths;
|
||||
private AuthzClient authzClient;
|
||||
private PathMatcher pathMatcher;
|
||||
|
||||
public AbstractPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
this.policyEnforcer = policyEnforcer;
|
||||
this.enforcerConfig = policyEnforcer.getEnforcerConfig();
|
||||
this.authzClient = policyEnforcer.getClient();
|
||||
this.pathMatcher = new PathMatcher();
|
||||
this.paths = policyEnforcer.getPaths();
|
||||
}
|
||||
|
||||
public AuthorizationContext authorize(OIDCHttpFacade httpFacade) {
|
||||
EnforcementMode enforcementMode = this.enforcerConfig.getEnforcementMode();
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(enforcementMode)) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
KeycloakSecurityContext securityContext = httpFacade.getSecurityContext();
|
||||
|
||||
if (securityContext != null) {
|
||||
AccessToken accessToken = securityContext.getToken();
|
||||
|
||||
if (accessToken != null) {
|
||||
Request request = httpFacade.getRequest();
|
||||
Response response = httpFacade.getResponse();
|
||||
String path = getPath(request);
|
||||
PathConfig pathConfig = this.pathMatcher.matches(path, this.paths);
|
||||
|
||||
LOGGER.debugf("Checking permissions for path [%s] with config [%s].", request.getURI(), pathConfig);
|
||||
|
||||
if (pathConfig == null) {
|
||||
if (EnforcementMode.PERMISSIVE.equals(enforcementMode)) {
|
||||
return createAuthorizationContext(accessToken);
|
||||
}
|
||||
|
||||
LOGGER.debugf("Could not find a configuration for path [%s]", path);
|
||||
response.sendError(403, "Could not find a configuration for path [" + path + "].");
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
if (EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
return createEmptyAuthorizationContext(true);
|
||||
}
|
||||
|
||||
PathConfig actualPathConfig = resolvePathConfig(pathConfig, request);
|
||||
Set<String> requiredScopes = getRequiredScopes(actualPathConfig, request);
|
||||
|
||||
if (isAuthorized(actualPathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
try {
|
||||
return createAuthorizationContext(accessToken);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing path [" + actualPathConfig.getPath() + "].", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!challenge(actualPathConfig, requiredScopes, httpFacade)) {
|
||||
LOGGER.debugf("Sending challenge to the client. Path [%s]", pathConfig);
|
||||
response.sendError(403, "Authorization failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return createEmptyAuthorizationContext(false);
|
||||
}
|
||||
|
||||
protected abstract boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade);
|
||||
|
||||
protected boolean isAuthorized(PathConfig actualPathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
Request request = httpFacade.getRequest();
|
||||
PolicyEnforcerConfig enforcerConfig = getEnforcerConfig();
|
||||
|
||||
if (isDefaultAccessDeniedUri(request, enforcerConfig)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AccessToken.Authorization authorization = accessToken.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Permission> permissions = authorization.getPermissions();
|
||||
boolean hasPermission = false;
|
||||
|
||||
for (Permission permission : permissions) {
|
||||
if (permission.getResourceSetId() != null) {
|
||||
if (isResourcePermission(actualPathConfig, permission)) {
|
||||
hasPermission = true;
|
||||
|
||||
if (actualPathConfig.isInstance() && !matchResourcePermission(actualPathConfig, permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
LOGGER.debugf("Authorization GRANTED for path [%s]. Permissions [%s].", actualPathConfig, permissions);
|
||||
if (request.getMethod().equalsIgnoreCase("DELETE") && actualPathConfig.isInstance()) {
|
||||
this.paths.remove(actualPathConfig);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (hasResourceScopePermission(requiredScopes, permission, actualPathConfig)) {
|
||||
hasPermission = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && EnforcementMode.PERMISSIVE.equals(actualPathConfig.getEnforcementMode())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.debugf("Authorization FAILED for path [%s]. No enough permissions [%s].", actualPathConfig, permissions);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDefaultAccessDeniedUri(Request request, PolicyEnforcerConfig enforcerConfig) {
|
||||
String accessDeniedPath = enforcerConfig.getOnDenyRedirectTo();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
if (request.getURI().contains(accessDeniedPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasResourceScopePermission(Set<String> requiredScopes, Permission permission, PathConfig actualPathConfig) {
|
||||
Set<String> allowedScopes = permission.getScopes();
|
||||
return (allowedScopes.containsAll(requiredScopes) || allowedScopes.isEmpty());
|
||||
}
|
||||
|
||||
protected AuthzClient getAuthzClient() {
|
||||
return this.authzClient;
|
||||
}
|
||||
|
||||
protected PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
protected PolicyEnforcer getPolicyEnforcer() {
|
||||
return policyEnforcer;
|
||||
}
|
||||
|
||||
private AuthorizationContext createEmptyAuthorizationContext(final boolean granted) {
|
||||
return new AuthorizationContext() {
|
||||
@Override
|
||||
public boolean hasPermission(String resourceName, String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasResourcePermission(String resourceName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScopePermission(String scopeName) {
|
||||
return granted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Permission> getPermissions() {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGranted() {
|
||||
return granted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PathConfig resolvePathConfig(PathConfig originalConfig, Request request) {
|
||||
String path = getPath(request);
|
||||
|
||||
if (originalConfig.hasPattern()) {
|
||||
ProtectedResource resource = this.authzClient.protection().resource();
|
||||
Set<String> search = resource.findByFilter("uri=" + path);
|
||||
|
||||
if (!search.isEmpty()) {
|
||||
// resource does exist on the server, cache it
|
||||
ResourceRepresentation targetResource = resource.findById(search.iterator().next()).getResourceDescription();
|
||||
PathConfig config = PolicyEnforcer.createPathConfig(targetResource);
|
||||
|
||||
config.setScopes(originalConfig.getScopes());
|
||||
config.setMethods(originalConfig.getMethods());
|
||||
config.setParentConfig(originalConfig);
|
||||
config.setEnforcementMode(originalConfig.getEnforcementMode());
|
||||
|
||||
this.policyEnforcer.addPath(config);
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
return originalConfig;
|
||||
}
|
||||
|
||||
private String getPath(Request request) {
|
||||
return request.getRelativePath();
|
||||
}
|
||||
|
||||
private Set<String> getRequiredScopes(PathConfig pathConfig, Request request) {
|
||||
Set<String> requiredScopes = new HashSet<>();
|
||||
|
||||
requiredScopes.addAll(pathConfig.getScopes());
|
||||
|
||||
String method = request.getMethod();
|
||||
|
||||
for (PolicyEnforcerConfig.MethodConfig methodConfig : pathConfig.getMethods()) {
|
||||
if (methodConfig.getMethod().equals(method)) {
|
||||
requiredScopes.addAll(methodConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return requiredScopes;
|
||||
}
|
||||
|
||||
private AuthorizationContext createAuthorizationContext(AccessToken accessToken) {
|
||||
return new AuthorizationContext(accessToken, this.paths);
|
||||
}
|
||||
|
||||
private boolean isResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
// first we try a match using resource id
|
||||
boolean resourceMatch = matchResourcePermission(actualPathConfig, permission);
|
||||
|
||||
// as a fallback, check if the current path is an instance and if so, check if parent's id matches the permission
|
||||
if (!resourceMatch && actualPathConfig.isInstance()) {
|
||||
resourceMatch = matchResourcePermission(actualPathConfig.getParentConfig(), permission);
|
||||
}
|
||||
|
||||
return resourceMatch;
|
||||
}
|
||||
|
||||
private boolean matchResourcePermission(PathConfig actualPathConfig, Permission permission) {
|
||||
return permission.getResourceSetId().equals(actualPathConfig.getId());
|
||||
}
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.resource.PermissionResource;
|
||||
import org.keycloak.authorization.client.resource.ProtectionResource;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class BearerTokenPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(BearerTokenPolicyEnforcer.class);
|
||||
|
||||
public BearerTokenPolicyEnforcer(PolicyEnforcer enforcer) {
|
||||
super(enforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
challengeUmaAuthentication(pathConfig, requiredScopes, facade);
|
||||
} else {
|
||||
challengeEntitlementAuthentication(facade);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void challengeEntitlementAuthentication(OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String clientId = authzClient.getConfiguration().getClientId();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/entitlement";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "KC_ETT realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\"");
|
||||
}
|
||||
|
||||
private void challengeUmaAuthentication(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
String ticket = getPermissionTicket(pathConfig, requiredScopes, authzClient);
|
||||
String clientId = authzClient.getConfiguration().getClientId();
|
||||
String authorizationServerUri = authzClient.getServerConfiguration().getIssuer().toString() + "/authz/authorize";
|
||||
response.setStatus(401);
|
||||
response.setHeader("WWW-Authenticate", "UMA realm=\"" + clientId + "\",as_uri=\"" + authorizationServerUri + "\",ticket=\"" + ticket + "\"");
|
||||
}
|
||||
|
||||
private String getPermissionTicket(PathConfig pathConfig, Set<String> requiredScopes, AuthzClient authzClient) {
|
||||
ProtectionResource protection = authzClient.protection();
|
||||
PermissionResource permission = protection.permission();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setScopes(requiredScopes);
|
||||
return permission.forResource(permissionRequest).getTicket();
|
||||
}
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.authorization.client.AuthorizationDeniedException;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationRequest;
|
||||
import org.keycloak.authorization.client.representation.AuthorizationResponse;
|
||||
import org.keycloak.authorization.client.representation.EntitlementRequest;
|
||||
import org.keycloak.authorization.client.representation.EntitlementResponse;
|
||||
import org.keycloak.authorization.client.representation.PermissionRequest;
|
||||
import org.keycloak.authorization.client.representation.PermissionResponse;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class KeycloakAdapterPolicyEnforcer extends AbstractPolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(KeycloakAdapterPolicyEnforcer.class);
|
||||
|
||||
public KeycloakAdapterPolicyEnforcer(PolicyEnforcer policyEnforcer) {
|
||||
super(policyEnforcer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAuthorized(PathConfig pathConfig, Set<String> requiredScopes, AccessToken accessToken, OIDCHttpFacade httpFacade) {
|
||||
AccessToken original = accessToken;
|
||||
|
||||
if (super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
accessToken = requestAuthorizationToken(pathConfig, requiredScopes, httpFacade);
|
||||
|
||||
if (accessToken == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessToken.Authorization authorization = original.getAuthorization();
|
||||
|
||||
if (authorization == null) {
|
||||
authorization = new AccessToken.Authorization();
|
||||
authorization.setPermissions(new ArrayList<Permission>());
|
||||
}
|
||||
|
||||
AccessToken.Authorization newAuthorization = accessToken.getAuthorization();
|
||||
|
||||
if (newAuthorization != null) {
|
||||
authorization.getPermissions().addAll(newAuthorization.getPermissions());
|
||||
}
|
||||
|
||||
original.setAuthorization(authorization);
|
||||
|
||||
return super.isAuthorized(pathConfig, requiredScopes, accessToken, httpFacade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean challenge(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade facade) {
|
||||
String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
|
||||
HttpFacade.Response response = facade.getResponse();
|
||||
|
||||
if (accessDeniedPath != null) {
|
||||
response.setStatus(302);
|
||||
response.setHeader("Location", accessDeniedPath);
|
||||
} else {
|
||||
response.sendError(403);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private AccessToken requestAuthorizationToken(PathConfig pathConfig, Set<String> requiredScopes, OIDCHttpFacade httpFacade) {
|
||||
try {
|
||||
String accessToken = httpFacade.getSecurityContext().getTokenString();
|
||||
AuthzClient authzClient = getAuthzClient();
|
||||
KeycloakDeployment deployment = getPolicyEnforcer().getDeployment();
|
||||
|
||||
if (getEnforcerConfig().getUserManagedAccess() != null) {
|
||||
LOGGER.debug("Obtaining authorization for authenticated user.");
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setScopes(requiredScopes);
|
||||
|
||||
PermissionResponse permissionResponse = authzClient.protection().permission().forResource(permissionRequest);
|
||||
AuthorizationRequest authzRequest = new AuthorizationRequest(permissionResponse.getTicket());
|
||||
AuthorizationResponse authzResponse = authzClient.authorization(accessToken).authorize(authzRequest);
|
||||
|
||||
if (authzResponse != null) {
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
|
||||
return null;
|
||||
} else {
|
||||
LOGGER.debug("Obtaining entitlements for authenticated user.");
|
||||
AccessToken token = httpFacade.getSecurityContext().getToken();
|
||||
|
||||
if (token.getAuthorization() == null) {
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).getAll(authzClient.getConfiguration().getClientId());
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
} else {
|
||||
EntitlementRequest request = new EntitlementRequest();
|
||||
PermissionRequest permissionRequest = new PermissionRequest();
|
||||
permissionRequest.setResourceSetId(pathConfig.getId());
|
||||
permissionRequest.setResourceSetName(pathConfig.getName());
|
||||
permissionRequest.setScopes(new HashSet<>(pathConfig.getScopes()));
|
||||
LOGGER.debugf("Sending entitlements request: resource_set_id [%s], resource_set_name [%s], scopes [%s].", permissionRequest.getResourceSetId(), permissionRequest.getResourceSetName(), permissionRequest.getScopes());
|
||||
request.addPermission(permissionRequest);
|
||||
EntitlementResponse authzResponse = authzClient.entitlement(accessToken).get(authzClient.getConfiguration().getClientId(), request);
|
||||
return AdapterRSATokenVerifier.verifyToken(authzResponse.getRpt(), deployment);
|
||||
}
|
||||
}
|
||||
} catch (AuthorizationDeniedException e) {
|
||||
LOGGER.debug("Authorization denied", e);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected error during authorization request.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
class PathMatcher {
|
||||
|
||||
private static final String ANY_RESOURCE_PATTERN = "/*";
|
||||
|
||||
PathConfig matches(final String requestedUri, Map<String, PathConfig> paths) {
|
||||
PathConfig pathConfig = paths.get(requestedUri);
|
||||
|
||||
if (pathConfig != null) {
|
||||
return pathConfig;
|
||||
}
|
||||
|
||||
PathConfig actualConfig = null;
|
||||
|
||||
for (PathConfig entry : paths.values()) {
|
||||
String protectedUri = entry.getPath();
|
||||
String selectedUri = null;
|
||||
|
||||
if (protectedUri.equals(ANY_RESOURCE_PATTERN) && actualConfig == null) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
int suffixIndex = protectedUri.indexOf(ANY_RESOURCE_PATTERN + ".");
|
||||
|
||||
if (suffixIndex != -1) {
|
||||
String protectedSuffix = protectedUri.substring(suffixIndex + ANY_RESOURCE_PATTERN.length());
|
||||
|
||||
if (requestedUri.endsWith(protectedSuffix)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (protectedUri.equals(requestedUri)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (protectedUri.endsWith(ANY_RESOURCE_PATTERN)) {
|
||||
String formattedPattern = removeWildCardsFromUri(protectedUri);
|
||||
|
||||
if (!formattedPattern.equals("/") && requestedUri.startsWith(formattedPattern)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (!formattedPattern.equals("/") && formattedPattern.endsWith("/") && formattedPattern.substring(0, formattedPattern.length() - 1).equals(requestedUri)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
int startRegex = protectedUri.indexOf('{');
|
||||
|
||||
if (startRegex != -1) {
|
||||
String prefix = protectedUri.substring(0, startRegex);
|
||||
|
||||
if (requestedUri.startsWith(prefix)) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedUri != null) {
|
||||
selectedUri = protectedUri;
|
||||
}
|
||||
|
||||
if (selectedUri != null) {
|
||||
if (actualConfig == null) {
|
||||
actualConfig = entry;
|
||||
} else {
|
||||
if (actualConfig.equals(ANY_RESOURCE_PATTERN)) {
|
||||
actualConfig = entry;
|
||||
}
|
||||
|
||||
if (protectedUri.startsWith(removeWildCardsFromUri(actualConfig.getPath()))) {
|
||||
actualConfig = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return actualConfig;
|
||||
}
|
||||
|
||||
private String removeWildCardsFromUri(String protectedUri) {
|
||||
return protectedUri.replaceAll("/[*]", "/");
|
||||
}
|
||||
}
|
||||
-234
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package org.keycloak.adapters.authorization;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.AuthorizationContext;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.authorization.client.AuthzClient;
|
||||
import org.keycloak.authorization.client.Configuration;
|
||||
import org.keycloak.authorization.client.representation.RegistrationResponse;
|
||||
import org.keycloak.authorization.client.representation.ResourceRepresentation;
|
||||
import org.keycloak.authorization.client.representation.ScopeRepresentation;
|
||||
import org.keycloak.authorization.client.resource.ProtectedResource;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
||||
import org.keycloak.representations.idm.authorization.Permission;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class PolicyEnforcer {
|
||||
|
||||
private static Logger LOGGER = Logger.getLogger(PolicyEnforcer.class);
|
||||
|
||||
private final KeycloakDeployment deployment;
|
||||
private final AuthzClient authzClient;
|
||||
private final PolicyEnforcerConfig enforcerConfig;
|
||||
private final Map<String, PathConfig> paths;
|
||||
|
||||
public PolicyEnforcer(KeycloakDeployment deployment, AdapterConfig adapterConfig) {
|
||||
this.deployment = deployment;
|
||||
this.enforcerConfig = adapterConfig.getPolicyEnforcerConfig();
|
||||
this.authzClient = AuthzClient.create(new Configuration(adapterConfig.getAuthServerUrl(), adapterConfig.getRealm(), adapterConfig.getResource(), adapterConfig.getCredentials(), deployment.getClient()));
|
||||
this.paths = configurePaths(this.authzClient.protection().resource(), this.enforcerConfig);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initialization complete. Path configurations:");
|
||||
for (PathConfig pathConfig : this.paths.values()) {
|
||||
LOGGER.debug(pathConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AuthorizationContext enforce(OIDCHttpFacade facade) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement is enable. Enforcing policy decisions for path [{0}].", facade.getRequest().getURI());
|
||||
}
|
||||
|
||||
AuthorizationContext context;
|
||||
|
||||
if (deployment.isBearerOnly()) {
|
||||
context = new BearerTokenPolicyEnforcer(this).authorize(facade);
|
||||
} else {
|
||||
context = new KeycloakAdapterPolicyEnforcer(this).authorize(facade);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debugv("Policy enforcement result for path [{0}] is : {1}", facade.getRequest().getURI(), context.isGranted() ? "GRANTED" : "DENIED");
|
||||
LOGGER.debugv("Returning authorization context with permissions:");
|
||||
for (Permission permission : context.getPermissions()) {
|
||||
LOGGER.debug(permission);
|
||||
}
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
PolicyEnforcerConfig getEnforcerConfig() {
|
||||
return enforcerConfig;
|
||||
}
|
||||
|
||||
AuthzClient getClient() {
|
||||
return authzClient;
|
||||
}
|
||||
|
||||
public Map<String, PathConfig> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
void addPath(PathConfig pathConfig) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
|
||||
KeycloakDeployment getDeployment() {
|
||||
return deployment;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configurePaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
boolean loadPathsFromServer = true;
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
if (!PolicyEnforcerConfig.EnforcementMode.DISABLED.equals(pathConfig.getEnforcementMode())) {
|
||||
loadPathsFromServer = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (loadPathsFromServer) {
|
||||
LOGGER.info("No path provided in configuration.");
|
||||
return configureAllPathsForResourceServer(protectedResource);
|
||||
} else {
|
||||
LOGGER.info("Paths provided in configuration.");
|
||||
return configureDefinedPaths(protectedResource, enforcerConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureDefinedPaths(ProtectedResource protectedResource, PolicyEnforcerConfig enforcerConfig) {
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
for (PathConfig pathConfig : enforcerConfig.getPaths()) {
|
||||
Set<String> search;
|
||||
String resourceName = pathConfig.getName();
|
||||
String path = pathConfig.getPath();
|
||||
|
||||
if (resourceName != null) {
|
||||
LOGGER.debugf("Trying to find resource with name [%s] for path [%s].", resourceName, path);
|
||||
search = protectedResource.findByFilter("name=" + resourceName);
|
||||
} else {
|
||||
LOGGER.debugf("Trying to find resource with uri [%s] for path [%s].", path, path);
|
||||
search = protectedResource.findByFilter("uri=" + path);
|
||||
}
|
||||
|
||||
if (search.isEmpty()) {
|
||||
if (enforcerConfig.isCreateResources()) {
|
||||
LOGGER.debugf("Creating resource on server for path [%s].", pathConfig);
|
||||
ResourceRepresentation resource = new ResourceRepresentation();
|
||||
|
||||
resource.setName(resourceName);
|
||||
resource.setType(pathConfig.getType());
|
||||
resource.setUri(path);
|
||||
|
||||
HashSet<ScopeRepresentation> scopes = new HashSet<>();
|
||||
|
||||
for (String scopeName : pathConfig.getScopes()) {
|
||||
ScopeRepresentation scope = new ScopeRepresentation();
|
||||
|
||||
scope.setName(scopeName);
|
||||
|
||||
scopes.add(scope);
|
||||
}
|
||||
|
||||
resource.setScopes(scopes);
|
||||
|
||||
RegistrationResponse registrationResponse = protectedResource.create(resource);
|
||||
|
||||
pathConfig.setId(registrationResponse.getId());
|
||||
} else {
|
||||
throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration.");
|
||||
}
|
||||
} else {
|
||||
pathConfig.setId(search.iterator().next());
|
||||
}
|
||||
|
||||
PathConfig existingPath = null;
|
||||
|
||||
for (PathConfig current : paths.values()) {
|
||||
if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) {
|
||||
existingPath = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingPath == null) {
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
} else {
|
||||
existingPath.getMethods().addAll(pathConfig.getMethods());
|
||||
existingPath.getScopes().addAll(pathConfig.getScopes());
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
private Map<String, PathConfig> configureAllPathsForResourceServer(ProtectedResource protectedResource) {
|
||||
LOGGER.info("Querying the server for all resources associated with this application.");
|
||||
Map<String, PathConfig> paths = Collections.synchronizedMap(new HashMap<String, PathConfig>());
|
||||
|
||||
for (String id : protectedResource.findAll()) {
|
||||
RegistrationResponse response = protectedResource.findById(id);
|
||||
ResourceRepresentation resourceDescription = response.getResourceDescription();
|
||||
|
||||
if (resourceDescription.getUri() != null) {
|
||||
PathConfig pathConfig = createPathConfig(resourceDescription);
|
||||
paths.put(pathConfig.getPath(), pathConfig);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static PathConfig createPathConfig(ResourceRepresentation resourceDescription) {
|
||||
PathConfig pathConfig = new PathConfig();
|
||||
|
||||
pathConfig.setId(resourceDescription.getId());
|
||||
pathConfig.setName(resourceDescription.getName());
|
||||
pathConfig.setPath(resourceDescription.getUri());
|
||||
|
||||
List<String> scopeNames = new ArrayList<>();
|
||||
|
||||
for (ScopeRepresentation scope : resourceDescription.getScopes()) {
|
||||
scopeNames.add(scope.getName());
|
||||
}
|
||||
|
||||
pathConfig.setScopes(scopeNames);
|
||||
pathConfig.setType(resourceDescription.getType());
|
||||
|
||||
return pathConfig;
|
||||
}
|
||||
}
|
||||
-236
@@ -1,236 +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.KeycloakPrincipal;
|
||||
import org.keycloak.adapters.AdapterUtils;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.rotation.AdapterRSATokenVerifier;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.FindFile;
|
||||
import org.keycloak.common.util.reflections.Reflections;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.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 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;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
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 = AdapterRSATokenVerifier.verifyToken(tokenString, deployment);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-45
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-187
@@ -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 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.adapters.authentication.ClientCredentialsProviderUtils;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.KeycloakUriBuilder;
|
||||
import org.keycloak.constants.ServiceUrlConstants;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.callback.CallbackHandler;
|
||||
import javax.security.auth.login.LoginException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Login module based on Resource Owner password credentials grant from OAuth2 specs. It's supposed to be used in environments. which
|
||||
* 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();
|
||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||
.append(", Error description: " + errorRep.getErrorDescription());
|
||||
}
|
||||
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) {
|
||||
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(is, OAuth2ErrorRepresentation.class);
|
||||
errorBuilder.append(", OAuth2 error. Error: " + errorRep.getError())
|
||||
.append(", Error description: " + errorRep.getErrorDescription());
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
-53
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.RSATokenVerifier;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class AdapterRSATokenVerifier {
|
||||
|
||||
private static final Logger log = Logger.getLogger(AdapterRSATokenVerifier.class);
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment) throws VerificationException {
|
||||
return verifyToken(tokenString, deployment, true, true);
|
||||
}
|
||||
|
||||
|
||||
public static PublicKey getPublicKey(String kid, KeycloakDeployment deployment) throws VerificationException {
|
||||
PublicKeyLocator pkLocator = deployment.getPublicKeyLocator();
|
||||
|
||||
PublicKey publicKey = pkLocator.getPublicKey(kid, deployment);
|
||||
if (publicKey == null) {
|
||||
log.errorf("Didn't find publicKey for kid: %s", kid);
|
||||
throw new VerificationException("Didn't find publicKey for specified kid");
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment, boolean checkActive, boolean checkTokenType) throws VerificationException {
|
||||
RSATokenVerifier verifier = RSATokenVerifier.create(tokenString).realmUrl(deployment.getRealmInfoUrl()).checkActive(checkActive).checkTokenType(checkTokenType);
|
||||
PublicKey publicKey = getPublicKey(verifier.getHeader().getKeyId(), deployment);
|
||||
return verifier.publicKey(publicKey).verify().getToken();
|
||||
}
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class HardcodedPublicKeyLocator implements PublicKeyLocator {
|
||||
|
||||
private PublicKey publicKey;
|
||||
|
||||
public HardcodedPublicKeyLocator(PublicKey publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset(KeycloakDeployment deployment) {
|
||||
|
||||
}
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.adapters.HttpAdapterUtils;
|
||||
import org.keycloak.adapters.HttpClientAdapterException;
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.util.JWKSUtils;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* When needed, publicKeys are downloaded by sending request to realm's jwks_url
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class JWKPublicKeyLocator implements PublicKeyLocator {
|
||||
|
||||
private static final Logger log = Logger.getLogger(JWKPublicKeyLocator.class);
|
||||
|
||||
private Map<String, PublicKey> currentKeys = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile int lastRequestTime = 0;
|
||||
|
||||
@Override
|
||||
public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {
|
||||
int minTimeBetweenRequests = deployment.getMinTimeBetweenJwksRequests();
|
||||
int publicKeyCacheTtl = deployment.getPublicKeyCacheTtl();
|
||||
int currentTime = Time.currentTime();
|
||||
|
||||
// Check if key is in cache.
|
||||
PublicKey publicKey = lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
||||
if (publicKey != null) {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
// Check if we are allowed to send request
|
||||
synchronized (this) {
|
||||
currentTime = Time.currentTime();
|
||||
if (currentTime > lastRequestTime + minTimeBetweenRequests) {
|
||||
sendRequest(deployment);
|
||||
lastRequestTime = currentTime;
|
||||
} else {
|
||||
log.debug("Won't send request to realm jwks url. Last request time was " + lastRequestTime);
|
||||
}
|
||||
|
||||
return lookupCachedKey(publicKeyCacheTtl, currentTime, kid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void reset(KeycloakDeployment deployment) {
|
||||
synchronized (this) {
|
||||
sendRequest(deployment);
|
||||
lastRequestTime = Time.currentTime();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private PublicKey lookupCachedKey(int publicKeyCacheTtl, int currentTime, String kid) {
|
||||
if (lastRequestTime + publicKeyCacheTtl > currentTime) {
|
||||
return currentKeys.get(kid);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void sendRequest(KeycloakDeployment deployment) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Going to send request to retrieve new set of realm public keys for client " + deployment.getResourceName());
|
||||
}
|
||||
|
||||
HttpGet getMethod = new HttpGet(deployment.getJwksUrl());
|
||||
try {
|
||||
JSONWebKeySet jwks = HttpAdapterUtils.sendJsonHttpRequest(deployment, getMethod, JSONWebKeySet.class);
|
||||
|
||||
Map<String, PublicKey> publicKeys = JWKSUtils.getKeysForUse(jwks, JWK.Use.SIG);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Realm public keys successfully retrieved for client " + deployment.getResourceName() + ". New kids: " + publicKeys.keySet().toString());
|
||||
}
|
||||
|
||||
// Update current keys
|
||||
currentKeys.clear();
|
||||
currentKeys.putAll(publicKeys);
|
||||
|
||||
} catch (HttpClientAdapterException e) {
|
||||
log.error("Error when sending request to retrieve realm keys", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.rotation;
|
||||
|
||||
import org.keycloak.adapters.KeycloakDeployment;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public interface PublicKeyLocator {
|
||||
|
||||
/**
|
||||
* @param kid
|
||||
* @param deployment
|
||||
* @return publicKey, which should be used for verify signature on given "input"
|
||||
*/
|
||||
PublicKey getPublicKey(String kid, KeycloakDeployment deployment);
|
||||
|
||||
/**
|
||||
* Reset the state of locator (eg. clear the cached keys)
|
||||
*
|
||||
* @param deployment
|
||||
*/
|
||||
void reset(KeycloakDeployment deployment);
|
||||
|
||||
}
|
||||
-19
@@ -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
|
||||
-91
@@ -1,91 +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.adapters.rotation.HardcodedPublicKeyLocator;
|
||||
import org.keycloak.adapters.rotation.JWKPublicKeyLocator;
|
||||
import org.keycloak.common.enums.RelativeUrlsUsed;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
import org.keycloak.enums.TokenStore;
|
||||
|
||||
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());
|
||||
|
||||
assertTrue(deployment.getPublicKeyLocator() instanceof HardcodedPublicKeyLocator);
|
||||
assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"),
|
||||
deployment.getPublicKeyLocator().getPublicKey(null, deployment));
|
||||
|
||||
assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString());
|
||||
assertEquals(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());
|
||||
assertEquals(10, deployment.getTokenMinimumTimeToLive());
|
||||
assertEquals(20, deployment.getMinTimeBetweenJwksRequests());
|
||||
assertEquals(120, deployment.getPublicKeyCacheTtl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadNoClientCredentials() throws Exception {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-no-credentials.json"));
|
||||
assertEquals(ClientIdAndSecretCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
|
||||
assertTrue(deployment.getPublicKeyLocator() instanceof JWKPublicKeyLocator);
|
||||
assertEquals(10, deployment.getMinTimeBetweenJwksRequests());
|
||||
assertEquals(86400, deployment.getPublicKeyCacheTtl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadJwtCredentials() throws Exception {
|
||||
KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/keycloak-jwt.json"));
|
||||
assertEquals(JWTClientCredentialsProvider.PROVIDER_ID, deployment.getClientAuthenticator().getId());
|
||||
}
|
||||
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package org.keycloak.adapters;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
/**
|
||||
* @author github.com/tubbynl
|
||||
*
|
||||
*/
|
||||
public class RefreshableKeycloakSecurityContextTest {
|
||||
|
||||
@Test
|
||||
public void isActive() {
|
||||
TokenMetadataRepresentation token = new TokenMetadataRepresentation();
|
||||
token.setActive(true);
|
||||
token.issuedNow();
|
||||
RefreshableKeycloakSecurityContext sut = new RefreshableKeycloakSecurityContext(null,null,null,token,null, null, null);
|
||||
|
||||
// verify false if null deployment (KEYCLOAK-3050; yielded a npe)
|
||||
assertFalse(sut.isActive());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -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,7 +0,0 @@
|
||||
{
|
||||
"realm": "demo",
|
||||
"resource": "customer-portal",
|
||||
"auth-server-url": "https://localhost:8443/auth",
|
||||
"public-client": true,
|
||||
"expose-token": true
|
||||
}
|
||||
@@ -1,35 +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",
|
||||
"token-minimum-time-to-live": 10,
|
||||
"min-time-between-jwks-requests": 20,
|
||||
"public-key-cache-ttl": 120
|
||||
}
|
||||
Binary file not shown.
@@ -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>2.5.4.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>
|
||||
-185
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
-46
@@ -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]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>2.5.4.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>
|
||||
-68
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.adapters.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.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve;
|
||||
import org.keycloak.adapters.tomcat.GenericPrincipalFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 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>2.5.4.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>
|
||||
-62
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
-63
@@ -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());
|
||||
}
|
||||
}
|
||||
-50
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-42
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-129
@@ -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();
|
||||
|
||||
// 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(deploymentUnit);
|
||||
boolean webRequiresKC = loginConfig != null && "KEYCLOAK".equalsIgnoreCase(loginConfig.getAuthMethod());
|
||||
boolean isConfigured = service.isDeploymentConfigured(deploymentUnit);
|
||||
|
||||
if ((hasSubsystemConfig && isConfigured) || webRequiresKC) {
|
||||
log.debug("Setting up KEYCLOAK auth method for WAR: " + deploymentUnit.getName());
|
||||
|
||||
// if secure-deployment configuration exists for web app, we force KEYCLOAK auth method on it
|
||||
if (hasSubsystemConfig) {
|
||||
addJSONData(service.getJSON(deploymentUnit), warMetaData);
|
||||
if (loginConfig != null) {
|
||||
loginConfig.setAuthMethod("KEYCLOAK");
|
||||
loginConfig.setRealmName(service.getRealmName(deploymentUnit));
|
||||
} else {
|
||||
log.warn("Failed to set up KEYCLOAK auth method for WAR: " + deploymentUnit.getName() + " (loginConfig == null)");
|
||||
}
|
||||
}
|
||||
addValve(webMetaData);
|
||||
KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentUnit.getName());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-231
@@ -1,231 +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.DeploymentUnit;
|
||||
import org.jboss.as.web.deployment.WarMetaData;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
import org.jboss.dmr.Property;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.metadata.web.jboss.JBossWebMetaData;
|
||||
|
||||
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(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
return deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||
|
||||
}
|
||||
|
||||
protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
if (! deployment.isDefined()) {
|
||||
return false;
|
||||
}
|
||||
ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName());
|
||||
return resource.isDefined();
|
||||
}
|
||||
|
||||
public String getJSON(DeploymentUnit deploymentUnit) {
|
||||
ModelNode deployment = getSecureDeployment(deploymentUnit);
|
||||
String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
|
||||
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(DeploymentUnit deploymentUnit) {
|
||||
//log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
|
||||
|
||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||
return this.secureDeployments.containsKey(deploymentName);
|
||||
}
|
||||
|
||||
private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) {
|
||||
String deploymentName = preferredDeploymentName(deploymentUnit);
|
||||
return this.secureDeployments.containsKey(deploymentName)
|
||||
? this.secureDeployments.get(deploymentName)
|
||||
: new ModelNode();
|
||||
}
|
||||
|
||||
// KEYCLOAK-3273: prefer module name if available
|
||||
private String preferredDeploymentName(DeploymentUnit deploymentUnit) {
|
||||
String deploymentName = deploymentUnit.getName();
|
||||
WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
|
||||
if (warMetaData == null) {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
|
||||
if (webMetaData == null) {
|
||||
return deploymentName;
|
||||
}
|
||||
|
||||
String moduleName = webMetaData.getModuleName();
|
||||
if (moduleName != null) return moduleName + ".war";
|
||||
|
||||
return deploymentName;
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +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();
|
||||
|
||||
if (!KeycloakAdapterConfigService.getInstance().isSecureDeployment(deploymentUnit)) {
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-36
@@ -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));
|
||||
}}
|
||||
-84
@@ -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);
|
||||
}
|
||||
}
|
||||
-59
@@ -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.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();
|
||||
}
|
||||
}
|
||||
-47
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-294
@@ -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-manager 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-manager 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-58
@@ -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-manager 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));
|
||||
}
|
||||
}
|
||||
-89
@@ -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);
|
||||
}
|
||||
}
|
||||
-41
@@ -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);
|
||||
}
|
||||
}
|
||||
-54
@@ -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.AbstractWriteAttributeHandler;
|
||||
import org.jboss.as.controller.AttributeDefinition;
|
||||
import org.jboss.as.controller.OperationContext;
|
||||
import org.jboss.as.controller.OperationFailedException;
|
||||
import org.jboss.dmr.ModelNode;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
-54
@@ -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));
|
||||
}
|
||||
}
|
||||
-154
@@ -1,154 +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.IntRangeValidator;
|
||||
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 SimpleAttributeDefinition TURN_OFF_CHANGE_SESSION =
|
||||
new SimpleAttributeDefinitionBuilder("turn-off-change-session-id-on-login", ModelType.BOOLEAN, true)
|
||||
.setXmlName("turn-off-change-session-id-on-login")
|
||||
.setAllowExpression(true)
|
||||
.setDefaultValue(new ModelNode(false))
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition TOKEN_MINIMUM_TIME_TO_LIVE =
|
||||
new SimpleAttributeDefinitionBuilder("token-minimum-time-to-live", ModelType.INT, true)
|
||||
.setXmlName("token-minimum-time-to-live")
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.setAllowExpression(true)
|
||||
.build();
|
||||
protected static final SimpleAttributeDefinition MIN_TIME_BETWEEN_JWKS_REQUESTS =
|
||||
new SimpleAttributeDefinitionBuilder("min-time-between-jwks-requests", ModelType.INT, true)
|
||||
.setXmlName("min-time-between-jwks-requests")
|
||||
.setValidator(new IntRangeValidator(-1, true))
|
||||
.setAllowExpression(true)
|
||||
.build();
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
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);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TURN_OFF_CHANGE_SESSION);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(TOKEN_MINIMUM_TIME_TO_LIVE);
|
||||
DEPLOYMENT_ONLY_ATTRIBUTES.add(MIN_TIME_BETWEEN_JWKS_REQUESTS);
|
||||
}
|
||||
|
||||
protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
-41
@@ -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);
|
||||
}
|
||||
}
|
||||
-59
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
-227
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-59
@@ -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;
|
||||
}
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.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);
|
||||
|
||||
}
|
||||
-34
@@ -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);
|
||||
}
|
||||
-18
@@ -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
|
||||
-91
@@ -1,91 +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.token-minimum-time-to-live=The adapter will refresh the token if the current token is expired OR will expire in 'token-minimum-time-to-live' seconds or less
|
||||
keycloak.secure-deployment.min-time-between-jwks-requests=If adapter recognize token signed by unknown public key, it will try to download new public key from keycloak server. However it won't try to download if already tried it in less than 'min-time-between-jwks-requests' seconds
|
||||
|
||||
keycloak.secure-deployment.credential=Credential value
|
||||
|
||||
keycloak.credential=Credential
|
||||
keycloak.credential.value=Credential value
|
||||
keycloak.credential.add=Credential add
|
||||
keycloak.credential.remove=Credential remove
|
||||
@@ -1,124 +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:element name="token-minimum-time-to-live" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="min-time-between-jwks-requests" type="xs:integer" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:all>
|
||||
<xs: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>
|
||||
-88
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>2.5.4.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>
|
||||
@@ -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>2.5.4.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>
|
||||
@@ -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>2.5.4.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>
|
||||
-31
@@ -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 {
|
||||
}
|
||||
-313
@@ -1,313 +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.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.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
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) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-218
@@ -1,218 +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.KeycloakSecurityContext;
|
||||
import org.keycloak.adapters.OIDCHttpFacade;
|
||||
import org.keycloak.adapters.spi.AuthenticationError;
|
||||
import org.keycloak.adapters.spi.LogoutError;
|
||||
import org.keycloak.common.util.HostUtils;
|
||||
|
||||
import javax.security.cert.X509Certificate;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.SecurityContext;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @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 String getRelativePath() {
|
||||
return requestContext.getUriInfo().getPath();
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
-94
@@ -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.jaxrs;
|
||||
|
||||
import org.keycloak.adapters.KeycloakConfigResolver;
|
||||
import org.keycloak.common.constants.GenericConstants;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
import javax.annotation.Priority;
|
||||
import javax.ws.rs.Priorities;
|
||||
import javax.ws.rs.container.PreMatching;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>2.5.4.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>
|
||||
-354
@@ -1,354 +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.UserIdentity;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
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.AuthenticatedActionsHandler;
|
||||
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.jetty.spi.JettyHttpFacade;
|
||||
import org.keycloak.adapters.jetty.spi.JettyUserSessionManagement;
|
||||
import org.keycloak.adapters.spi.AuthChallenge;
|
||||
import org.keycloak.adapters.spi.AuthOutcome;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
import org.keycloak.constants.AdapterConstants;
|
||||
import org.keycloak.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.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);
|
||||
request.setAuthentication(authentication);
|
||||
}
|
||||
return authentication;
|
||||
}
|
||||
|
||||
protected abstract Authentication createAuthentication(UserIdentity userIdentity, Request request);
|
||||
|
||||
public static abstract class KeycloakAuthentication extends UserAuthentication {
|
||||
public KeycloakAuthentication(String method, UserIdentity userIdentity) {
|
||||
super(method, userIdentity);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-129
@@ -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.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
-102
@@ -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.KeycloakDeployment;
|
||||
import org.keycloak.adapters.OAuthRequestAuthenticator;
|
||||
import org.keycloak.adapters.OidcKeycloakAccount;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.adapters.RequestAuthenticator;
|
||||
import org.keycloak.adapters.spi.HttpFacade;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Loaded 100 of 7537 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user